<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Artyom Keydunov</title>
    <description>The latest articles on DEV Community by Artyom Keydunov (@keydunov).</description>
    <link>https://dev.to/keydunov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F115690%2Fab927dcc-522c-4aa2-8e4c-8abc05a5029d.jpg</url>
      <title>DEV Community: Artyom Keydunov</title>
      <link>https://dev.to/keydunov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/keydunov"/>
    <language>en</language>
    <item>
      <title>What is Embedded Analytics?</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 22 Sep 2022 19:58:06 +0000</pubDate>
      <link>https://dev.to/cubejs/what-is-embedded-analytics-24m0</link>
      <guid>https://dev.to/cubejs/what-is-embedded-analytics-24m0</guid>
      <description>&lt;p&gt;We’ve previously discussed the &lt;a href="https://cube.dev/blog/modern-embedded-analytics"&gt;modern data stack for embedded analytics&lt;/a&gt;—but what is embedded analytics? Let’s take a step back and review this popular category of data application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedded Analytics Definition
&lt;/h2&gt;

&lt;p&gt;Embedded analytics is the integration of data analytics and visualization capabilities within a user’s natural workflow—within internal tooling, web portals, and products like apps.&lt;/p&gt;

&lt;p&gt;Such an integration enables users to conduct analytics directly inside the applications they use rather than having to switch between various applications to get insights.&lt;/p&gt;

&lt;p&gt;Let’s illustrate with an example: a seller on an e-commerce platform, “TrapezoidSpace,” would like insights about their revenue.&lt;/p&gt;

&lt;p&gt;Without embedded analytics on the e-commerce platform, the seller would have to manually request a sales report from the e-commerce platform, import it into secondary analytics software, then work with the data to get the necessary insights. &lt;/p&gt;

&lt;p&gt;However, with embedded analytics, they could get the insights they need with a real-time dashboard visualizing revenue presented to them directly within the e-commerce platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedded Analytics Use Cases
&lt;/h2&gt;

&lt;p&gt;There are many use cases for embedded analytics. We can divide them by the end users and the types of information they present.&lt;/p&gt;

&lt;h3&gt;
  
  
  External vs. Internal Embedded Analytics
&lt;/h3&gt;

&lt;p&gt;Embedded analytics can be internal-facing—meaning the end user is within an organization—or external-facing, meaning the end user is an organization’s customer.&lt;/p&gt;

&lt;p&gt;Take our previous example of TrapezoidSpace: here, the seller is an external end-user of the platform’s embedded analytics features.&lt;/p&gt;

&lt;p&gt;In contrast, let’s take the example of TrapezoidSpace’s customer success manager. They track the time-to-resolution of tickets (TTR) using a bespoke ticket management system that visualizes trends in TTR. That would be an example of internal-facing embedded analytics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of Embedded Analytics
&lt;/h3&gt;

&lt;p&gt;People use embedded analytics in many ways, but real-time reports and dashboards are the most common.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-time static and interactive reports&lt;/strong&gt; are tabular views of information with the ability to refine data by setting parameters and scheduling. In contrast, &lt;strong&gt;dashboards and data visualizations&lt;/strong&gt; are charts and graphs that present and visualize metrics to make finding insights and trends easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedded Analytics Benefits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Generally, embedded analytics contributes to a user-centric UX that adds value and drives adoption.
&lt;/h3&gt;

&lt;p&gt;Whether users are external or internal, the point of an investment in any tooling is to bring value and thereby drive the tool’s adoption. And, providing a user-centric design and seamless UX tailored to the target user is one of the only ways to get people to (willingly) use products.&lt;/p&gt;

&lt;p&gt;So, conveniently locating BI and analytics functionality within the user’s workflow significantly enhances a product’s UX. Improved UX promotes the more effective use of the tool, creates greater value, and increases the tool’s adoption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Externally, embedded analytics increases revenue and competitive advantage.
&lt;/h3&gt;

&lt;p&gt;In a survey of project managers, developers, and executives, &lt;a href="https://c6abb8db-514c-4f5b-b5a1-fc710f1e464e.filesusr.com/ugd/e5361a_76709448ddc6490981f0cbea42d51508.pdf"&gt;96% reported that embedded analytics contributes to revenue growth&lt;/a&gt;. They credited this to better UX and their ability to add revenue streams based on highly sought-after functionalities.&lt;/p&gt;

&lt;p&gt;Adding high-value capabilities to products allowed businesses to introduce tiered or subscription-based analytics services—opening the door to acquiring new customers and upselling to recurring ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internally, embedded analytics increases productivity and the accessibility of data-driven decision-making.
&lt;/h3&gt;

&lt;p&gt;The efficiency with which users can conduct analysis with embedded analytics significantly boosts time effectiveness (and, therefore, cost-effectiveness). With auto-generated dashboards and filtering capabilities, users can draw insights faster and avoid the cumbersome processes of manually pulling data and conducting analysis in separate applications.&lt;/p&gt;

&lt;p&gt;Easy access to analytics also allows those with less experience in data analysis to make data-driven decisions. In a 2022 survey, &lt;a href="https://www.techtarget.com/searchdatamanagement/feature/Improve-data-value-by-relying-on-economic-principles"&gt;92% of companies cited their biggest obstacle in becoming ‘data-driven’&lt;/a&gt; as people, processes, and change management. However, a custom-built internal embedded analytics platform—one that is tailored to the team’s workflow—doesn’t require complex training in generic BI tools. &lt;/p&gt;

&lt;p&gt;Rather than having to facilitate learning of generic BI tools which aren’t set in its work’s context, an organization can create tools that are already infused with the working context. And so, instrumenting intuitively tailored tools powered by embedded analytics removes training bottlenecks and dependencies—increasing productivity and value-add. &lt;/p&gt;

&lt;h2&gt;
  
  
  Embedded Analytics vs. Business Intelligence
&lt;/h2&gt;

&lt;p&gt;The difference between embedded analytics and traditional business intelligence is simple. While traditional BI means you conduct your analytics in a separate set of tools, embedded analytics allows users to access insights in-app. Their data visualizations, dashboards, and reports live in the application in which they conduct operations, allowing for real-time refreshes and faster learning.&lt;/p&gt;

&lt;p&gt;This contrast may seem somewhat minute, but it actually saves employees a lot of time—up to &lt;a href="https://netstorage.ringcentral.com/documents/connected_workplace.pdf"&gt;5 hours&lt;/a&gt; a week. These savings increase productivity, allow for constantly updated information, and streamline the entire analytics workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SAR3r9c6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/d37e69cf-2bec-4469-997b-8782fa6c5d17/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SAR3r9c6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/d37e69cf-2bec-4469-997b-8782fa6c5d17/" alt="embedded analytics MDS" width="880" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Cube can help you power your Embedded Analytics
&lt;/h2&gt;

&lt;p&gt;Cube is a headless business intelligence tool that accesses data from &lt;a href="https://cube.dev/docs/config/databases"&gt;raw data sources&lt;/a&gt;, &lt;a href="https://cube.dev/docs/schema/getting-started"&gt;models the data&lt;/a&gt;, &lt;a href="https://cube.dev/docs/caching"&gt;caches&lt;/a&gt; query results, and makes the data available to downstream data applications via &lt;a href="https://cube.dev/docs/rest-api"&gt;REST&lt;/a&gt;, &lt;a href="https://cube.dev/docs/backend/graphql"&gt;GraphQL&lt;/a&gt;, and&lt;a href="https://cube.dev/docs/backend/sql"&gt; SQL APIs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;“Headlessness” refers to tooling that separates its functionalities from the end user’s interface—making it the perfect basis for building branded and customizable embedded analytics for any application.&lt;/p&gt;

&lt;p&gt;Cube is unique in that it provides a centralized, upstream semantic layer to define metrics, making all of the analytics it powers consistent and accurate. This semantic layer and its caching capabilities make sure that an application is performant and cost-effectively provides correct, up-to-date information to all of the users who access it.&lt;/p&gt;

&lt;p&gt;Similarly, Cube’s centralized data access control supports &lt;a href="https://cube.dev/docs/multitenancy-setup#multitenancy"&gt;multi-tenancy&lt;/a&gt; and allows &lt;a href="https://cube.dev/docs/recipes/role-based-access#enforcing-role-based-access"&gt;role-based&lt;/a&gt; and &lt;a href="https://cube.dev/docs/recipes/column-based-access#enforcing-column-based-access"&gt;column-based governance&lt;/a&gt;, which ensures users are only accessing data appropriate to them. This type of security is especially crucial in external-facing embedded analytics, where many separate entities—for example, each seller on TrapezoidSpace—need to access their data and their data only. &lt;/p&gt;

&lt;p&gt;Cube enables developers to build consistent, secure, and performant embedded analytics efficiently; dashboards in hours, not weeks.&lt;/p&gt;

&lt;p&gt;It can be the foundation for building embedded analytics applications with its &lt;a href="https://cube.dev/docs/config/downstream"&gt;compatibility with many popular front-end tools&lt;/a&gt; and business applications. So many, in fact, that we built &lt;a href="https://awesome.cube.dev/"&gt;a wiki about our favorite tools&lt;/a&gt; in the modern data stack—including tools for building embedded analytics (aptly named “awesome.cube.dev”). &lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;p&gt;Want to learn more about how to build embedded analytics applications with Cube or headless BI? &lt;/p&gt;

&lt;p&gt;Get in touch with us and the rest of the Cube community on &lt;a href="https://slack.cube.dev/"&gt;Slack&lt;/a&gt; or &lt;a href="https://github.com/cube-js/cube.js"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the mood for some hands-on help with building a modern embedded analytics data stack? &lt;a href="https://cube.dev/contact"&gt;Schedule a time to chat with us 1:1.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>embeddedanalytics</category>
      <category>dataapplication</category>
      <category>ux</category>
      <category>headlessbi</category>
    </item>
    <item>
      <title>What are data apps?</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 22 Sep 2022 19:55:58 +0000</pubDate>
      <link>https://dev.to/cubejs/what-are-data-apps-4kb2</link>
      <guid>https://dev.to/cubejs/what-are-data-apps-4kb2</guid>
      <description>&lt;p&gt;Previously, we’ve &lt;a href="https://cube.dev/blog/headless-bi"&gt;described the parts of headless BI&lt;/a&gt;, taken an &lt;a href="https://cube.dev/blog/open-source-data-modeling"&gt;in-depth look at the data modeling layer&lt;/a&gt;, and explored one use case for headless BI: &lt;a href="https://cube.dev/blog/what-is-embedded-analytics"&gt;embedded analytics&lt;/a&gt;. This week, let’s take a step back and look at the category of data applications.&lt;/p&gt;

&lt;p&gt;But first…&lt;/p&gt;

&lt;h2&gt;
  
  
  What are data applications?
&lt;/h2&gt;

&lt;p&gt;“Data apps” is an umbrella term for a category of interactive tools that use data to deliver insight or automatically take action. When we talk about data apps, we frequently cite the examples of recommendation engines, data visualization built into applications, and customized internal reporting tools for business teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isn’t this just embedded analytics?
&lt;/h2&gt;

&lt;p&gt;Embedded analytics takes the kind of exploration that used to happen in dashboards and legacy BI tools, and injects it directly into the applications that internal teams and external customers already use. Headless BI facilitates building embedded analytics more quickly. But embedded analytics is just the beginning.&lt;/p&gt;

&lt;p&gt;Despite being more accessible and customized than traditional dashboards, embedded analytics is still primarily a tool for data exploration. By contrast, data applications are capable of data explanation: highlighting trends, surfacing insights, making recommendations. This type of application entails a dynamic, purpose-built user experience, and it is typically developed by software and data engineers, not business analysts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are some use cases of data applications?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The first type of data applications is an embedded data app.&lt;/strong&gt; Think of this as the evolution of embedded analytics, but unlike embedded analytics’ static dashboards, embedded data features tend to be highly customized, dynamic, and purpose-built. These applications surface insight within the native user experience of another application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A business’s internal data products and portals are a second kind of data applications.&lt;/strong&gt; Unlike traditional or embedded exploration dashboards, this type of data application is purpose-built for a specific business unit, and is built with relevant business context. These applications’ custom interactivity allows business users to receive insights without mastering data analysts’ workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third type of data applications are end-consumer-facing applications.&lt;/strong&gt; These may be built for customers, partners, or shareholders, and they are not dissimilar from internal applications—but they tend to require a finer level of design polish and customization. Additionally, this type of app must be built for higher performance, reflecting consumers’ expectations of speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z2XvPvES--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/f3b2607c-8d11-489e-a09e-df9f94c1384c/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z2XvPvES--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/f3b2607c-8d11-489e-a09e-df9f94c1384c/" alt="cube data applications schema" width="880" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How are data applications built?
&lt;/h2&gt;

&lt;p&gt;By their nature, data apps require recourse to large quantities of data. This has been made possible by the rise of the cloud data warehouse and an ever-growing ecosystem of data ingestion, governance, transformation, and orchestration tools.&lt;/p&gt;

&lt;p&gt;But given their complexity and power, data apps generally are built by engineering teams, and they require integration with modern engineering workflows, including version control, testing, and continuous integration and deployment practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building from scratch
&lt;/h3&gt;

&lt;p&gt;Embedding data app functionality into a larger application generally requires building from scratch. What is the architecture of such a solution? &lt;/p&gt;

&lt;h4&gt;
  
  
  Data store
&lt;/h4&gt;

&lt;p&gt;Naturally, a data application starts with the data—and the basis of the modern data stack is the cloud data warehouse. This can be a general purpose data warehouse like &lt;a href="https://snowflake.com"&gt;Snowflake&lt;/a&gt; or a real-time tool like &lt;a href="https://www.firebolt.io/"&gt;Firebolt&lt;/a&gt;, &lt;a href="https://clickhouse.com/"&gt;ClickHouse&lt;/a&gt;, or &lt;a href="https://materialize.com/"&gt;Materialize&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Headless BI layer
&lt;/h4&gt;

&lt;p&gt;A crucial component of a data app is the &lt;a href="https://cube.dev/blog/headless-bi"&gt;headless BI layer&lt;/a&gt;. Specifically, a major piece of this is &lt;strong&gt;access control&lt;/strong&gt; integrated with the warehouse’s security controls, because embedded analytics always require multitenancy. A second piece is &lt;strong&gt;advanced caching&lt;/strong&gt;. This is because the data warehouse is a great candidate for a backend, but itself does not support highly concurrent queries with sub-second latency that modern data consumers expect.&lt;/p&gt;

&lt;p&gt;The BI layer is also where &lt;strong&gt;data modeling&lt;/strong&gt; is handled, to ensure that a data app’s users consume the same data definitions as users of other internal or external applications. Data modeling and metrics definition should be handled once, and this must be up-stack from every application or dashboard.&lt;/p&gt;

&lt;p&gt;Data is then made available via diverse APIs—e.g., SQL, GraphQL, and REST—to be consumed by…&lt;/p&gt;

&lt;h4&gt;
  
  
  A hybrid presentation layer
&lt;/h4&gt;

&lt;p&gt;For the high customization expected of an embedded data application, and when front-end teams are looped in, different charting libraries can be used. These range from &lt;a href="https://d3js.org/"&gt;D3&lt;/a&gt; to &lt;a href="https://www.chartjs.org/"&gt;Chart.js&lt;/a&gt; and &lt;a href="https://www.highcharts.com/"&gt;Highcharts&lt;/a&gt;. These most likely will be natively integrated with frontend application frameworks like &lt;a href="http://react"&gt;React&lt;/a&gt; or &lt;a href="https://angularjs.org/"&gt;Angular&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with a framework
&lt;/h3&gt;

&lt;p&gt;For the second and third types of data applications, the initial layers of the data stack are the same—i.e., the base layer is a data warehouse, followed by a headless BI layer for data modeling, access control, caching, and application APIs.&lt;/p&gt;

&lt;p&gt;For the user interface, however, there’s typically less customization required. This creates the opportunity to take advantage of the new category of no code / low code tools like &lt;a href="https://www.appsmith.com/"&gt;Appsmith&lt;/a&gt; and &lt;a href="https://retool.com/"&gt;Retool&lt;/a&gt;, which can be used to quickly build analytics interfaces.&lt;/p&gt;

&lt;p&gt;There also are data application frameworks that are helpful here: tools like &lt;a href="https://plotly.com/dash/"&gt;Plotly Dash&lt;/a&gt; and &lt;a href="https://streamlit.io"&gt;Streamlit&lt;/a&gt; make it possible to turn data scripts into shareable web applications without the need for front-end development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;As it gets easier to build customized experiences, the number and types of data apps will proliferate—but the use case for a basic dashboard-centric experience won’t go away. There will always be cases where needs are best met with traditional charts, or when the quick turnaround requires making something available without tapping engineering resources for help. For these, embedded analytics are and will remain the best choice.&lt;/p&gt;

&lt;p&gt;What’s exciting, though, is all of the new opportunities that the modern data application stack makes available. Opportunities for working with ever greater quantities of data, with ever greater complexity, will only grow.&lt;/p&gt;

</description>
      <category>dataapps</category>
      <category>dataapplications</category>
      <category>frontend</category>
      <category>dataanalytics</category>
    </item>
    <item>
      <title>Embedded Analytics with Tableau and Cube</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 22 Sep 2022 19:52:56 +0000</pubDate>
      <link>https://dev.to/cubejs/embedded-analytics-with-tableau-and-cube-344a</link>
      <guid>https://dev.to/cubejs/embedded-analytics-with-tableau-and-cube-344a</guid>
      <description>&lt;p&gt;These days, customer-facing analytics are everywhere. &lt;/p&gt;

&lt;p&gt;We are, after all, in The Data Age—hence why you’re reading this blog and why we’re writing it. And because of the ubiquity of data, as consumers, we’ve become extraordinarily curious; we want to know everything about our businesses, investments, and lap times. &lt;/p&gt;

&lt;p&gt;Consumers are pining for insights—presented in fast-loading, beautifully customized dashboards. And so, many companies are turning to embedded analytics with &lt;a href="https://www.tableau.com/"&gt;Tableau&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;But as more consumers demand analytics, raising the standards of UI customization, interactivity, and speed, what if there were a more tailored, more performant way to give users the insights they need?&lt;/p&gt;

&lt;p&gt;Well, there is: embedded analytics with Tableau and &lt;a href="https://cube.dev/"&gt;Cube&lt;/a&gt;. Let’s explain. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Embedded Analytics?
&lt;/h2&gt;

&lt;p&gt;Simply put, &lt;a href="https://cube.dev/blog/what-is-embedded-analytics"&gt;embedded analytics&lt;/a&gt; is the practice of integrating data analytics and visualization capabilities with a user’s natural workflow, such as their web portals, internal tools, or applications. &lt;/p&gt;

&lt;p&gt;For example: with embedded analytics, rather than needing to export sales data from their e-commerce platform and then conduct analysis separately in Excel, a seller could derive insights from dashboards and reports available to them directly within their commerce portal. &lt;/p&gt;

&lt;p&gt;Of course, embedded analytics &lt;strong&gt;can be used in internal- and external-facing applications to provide real-time static and interactive reports, dashboards, and data visualizations&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;There are many benefits of using embedded analytics. First, it can &lt;strong&gt;save the user time and effort&lt;/strong&gt; and add a lot of value. In turn, this saved time and gleaned insights &lt;strong&gt;contribute to a user-centric UX that drives adoption&lt;/strong&gt;—even among those who aren’t as analytics-experienced. And higher adoption means a &lt;strong&gt;better ROI on internal tooling&lt;/strong&gt; and an &lt;strong&gt;increase in revenue for external tooling&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Embedded Analytics with Tableau
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m6XXbYVl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/1b6df403-1884-4e4d-83b1-b26788833ef4/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m6XXbYVl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/1b6df403-1884-4e4d-83b1-b26788833ef4/" alt="embedded analytics with tableau schema" width="880" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, why do so many companies turn to &lt;a href="https://www.tableau.com/products/embedded-analytics"&gt;embedded analytics with Tableau&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Tableau is one of the leading platforms for data visualization and dashboarding. It's based on its own VizQL, which expresses data visually by translating drag-and-drop actions into data queries. &lt;/p&gt;

&lt;p&gt;This drag-and-drop capability makes data analytics and business intelligence accessible to a much wider pool of people—which is a great thing. Clearly, given its smashing success, Tableau met a hot demand for data democratization and did it well. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;However&lt;/em&gt;, things get a little more hinky when it comes to the &lt;a href="https://cube.dev/blog/modern-embedded-analytics"&gt;embedded analytics stack&lt;/a&gt;. This is because users not only use Tableau for analytics within Tableau itself but also use it to power embedded analytics within other applications. &lt;/p&gt;

&lt;p&gt;The problem is that &lt;strong&gt;having your embedded analytics with Tableau sit directly on top of your data warehouse comes with a few shortcomings&lt;/strong&gt;, such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Difficulty with customizability and designing user-centric UIs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Duplicated effort and opportunities for inconsistency when creating multiple sets of data definitions and security contexts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Weaker analytics performance&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1) Difficulty with customizability and designing user-centric UIs
&lt;/h3&gt;

&lt;p&gt;Tableau has many visualization layer options, but it just doesn’t have one for every audience or use case. You can undoubtedly brand the Tableau visualizations embedded in your app; however, there are restrictions on what you can build with the visualization layers Tableau offers (e.g., extremely use-case-specific drill-downs).&lt;/p&gt;

&lt;p&gt;And so, &lt;strong&gt;Tableau’s visualization templates may work just fine for 80% of your applications, but those that truly need a highly tailored, custom UI will require you to build a custom presentation layer from scratch&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The problem is that using multiple different visualization layers together with Tableau—meaning, both custom-built and from Tableau—leads to…&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Duplicated effort and opportunities for inconsistency
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UmlcKAJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/78d63d2b-23b9-48f4-bb96-8bab43cc7ded/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UmlcKAJ0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/78d63d2b-23b9-48f4-bb96-8bab43cc7ded/" alt="embedded analytics with tableau problems" width="880" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you want to use Tableau and a separate charting library to build a custom presentation layer? That makes perfect sense when you want to combine the advantages of Tableau’s presentation layers with the flexibility of a custom UI. &lt;/p&gt;

&lt;p&gt;However, the challenge lies in consistently orchestrating data modeling, security, and caching across all of your presentation layers. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By connecting multiple presentation layers to your data warehouse, you’ll need to individually orchestrate your data models and access controls so they’re uniformly defined&lt;/strong&gt;. That’s a lot of duplicated effort—which opens the door to gaps and errors. &lt;/p&gt;

&lt;h3&gt;
  
  
  3) Weaker analytics performance
&lt;/h3&gt;

&lt;p&gt;Lastly, powering embedded analytics with Tableau without a BI layer between Tableau and the data warehouse can cause latency in your user-facing visualizations and dashboards.&lt;/p&gt;

&lt;p&gt;Latency is a true user adoption killer: a Google study showed that &lt;a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/mobile-page-speed-new-industry-benchmarks/"&gt;bounce likelihood increased by 32%&lt;/a&gt; when a page’s load time increased from one second to three, and by &lt;em&gt;90%&lt;/em&gt; when page load time increased from one to five seconds. So, performance is essential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In a stack in which Tableau’s presentation layer sits directly on top of your data source, your users request data from the data source for every query&lt;/strong&gt;. At best, &lt;strong&gt;this is redundant and costly&lt;/strong&gt;, as multiple users may be requesting the same information. &lt;/p&gt;

&lt;p&gt;At worst, however, building a stack like this &lt;strong&gt;can result in latency and lower performance&lt;/strong&gt;. Request volume across users is unpredictable; you can’t control how many people simultaneously request data or consume visualizations. And so, if you have multiple presentation layers, each with duplicate data modeling, security, and caching layers, each making separate and redundant requests directly of the data source—that opens the door to latency.&lt;/p&gt;

&lt;p&gt;Latency can be a problem even if you only use Tableau (don’t worry, we happen to have &lt;a href="https://cube.dev/cloud"&gt;a fix&lt;/a&gt;). But, with multiple presentation layers, the chance of uneven performance across them is much higher. &lt;/p&gt;

&lt;h2&gt;
  
  
  The best of both worlds: embedded analytics with Tableau and Cube
&lt;/h2&gt;

&lt;p&gt;Alright—let’s get to the solution. &lt;/p&gt;

&lt;p&gt;Tableau is a fantastic tool (in fact, here’s &lt;a href="https://cube.dev/blog/tableau-tutorial"&gt;how to use Tableau with Cube&lt;/a&gt;.) Combining it with custom presentation layers is a great way to get both efficient, out-of-the-box visualizations and user-centric UI for your embedded analytics. &lt;/p&gt;

&lt;p&gt;So how can you fix the issues above? With upstream, centralized data modeling, security, and caching orchestration. Hi, we’re &lt;a href="https://cube.dev/cloud"&gt;Cube&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Cube streamlines building embedded analytics with Tableau and custom presentation layers
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--01q8Aej9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/b52d016a-8ca8-4cdb-85aa-765669ff781c/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--01q8Aej9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/b52d016a-8ca8-4cdb-85aa-765669ff781c/" alt="embedded analytics with tableau and cube" width="880" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube is a &lt;a href="https://cube.dev/blog/headless-bi"&gt;headless BI&lt;/a&gt; platform that enables your data modeling, data access control, and caching layers to live upstream of every presentation layer and data application in your stack. &lt;/p&gt;

&lt;p&gt;So, rather than individually orchestrating r&lt;a href="https://cube.dev/docs/recipes/role-based-access#enforcing-role-based-access"&gt;ole-based access controls&lt;/a&gt; or metrics definitions for each presentation layer, you’re centralizing and defining them once—removing duplicated effort and its inevitable inconsistencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cube ensures uniformly performant embedded analytics powered by Tableau and custom presentation layers
&lt;/h3&gt;

&lt;p&gt;Cube’s robust caching layer manages the throughput of data requests and acts as a buffer between your data source and your data application. Its advanced &lt;a href="https://cube.dev/docs/caching#pre-aggregations"&gt;pre-aggregation capabilities&lt;/a&gt;, query queue, and &lt;a href="https://cube.dev/docs/caching#in-memory-cache-refresh-keys"&gt;refresh keys&lt;/a&gt; protect your data warehouse from redundant, costly, and latency-causing queries. &lt;/p&gt;

&lt;p&gt;Designed to protect the data source from the possibility of even an unlimited number of concurrent requests, Cube can significantly speed up your embedded Tableau dashboards and visualizations. &lt;/p&gt;

&lt;p&gt;Cube also makes embedded analytics more performant even when built with a combination of Tableau and custom presentation layers. By allowing Cube to do the heavy lifting of data modeling, security, and caching orchestration, the presentation layers become very “thin”—essentially, not needing to process much. This enables them to become uniformly performant and allows the application’s embedded analytics to become more performant as a whole. &lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;p&gt;Interest piqued? We thought it might be. &lt;/p&gt;

&lt;p&gt;Give your users a performant, data-rich embedded analytics experience with Tableau and Cube—especially since &lt;a href="https://cube.dev/cloud"&gt;Cube is free to use&lt;/a&gt; and we already have a &lt;a href="https://cube.dev/blog/tableau-tutorial"&gt;tutorial for using Tableau with Cube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get in touch with us and the rest of the Cube community on &lt;a href="https://slack.cube.dev/"&gt;Slack&lt;/a&gt; or &lt;a href="https://github.com/cube-js/cube.js"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the mood for some hands-on help with building a modern embedded analytics data stack? &lt;a href="https://cube.dev/contact"&gt;Schedule a time to chat with us 1:1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>embeddedanalytics</category>
      <category>headlessbi</category>
      <category>tableau</category>
      <category>dataanalytics</category>
    </item>
    <item>
      <title>Headless BI with streaming data</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 22 Sep 2022 19:45:23 +0000</pubDate>
      <link>https://dev.to/cubejs/headless-bi-with-streaming-data-4daf</link>
      <guid>https://dev.to/cubejs/headless-bi-with-streaming-data-4daf</guid>
      <description>&lt;p&gt;Demand to make data operational and real-time is ever-growing. Both consumers and enterprises expect applications to react to changes in real time, and be proactive with intelligent notifications and alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can Cube help?
&lt;/h2&gt;

&lt;p&gt;At Cube, our primary &lt;a href="https://cube.dev/docs/config/databases"&gt;sources of data&lt;/a&gt; have been cloud data warehouses with batched data—but since our first days, we have wanted to work with streaming data too. Our ambition has been to create a seamless experience for data engineers to build applications on top of a single, headless data access layer, regardless of whether the data is a stream, a batch, or a union of both.&lt;/p&gt;

&lt;p&gt;Today, we’re happy to announce a major step in building streaming support into Cube, and to share our plans for what’s next.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We are hosting an online event to reveal these features. &lt;a href="https://cube.dev/events/building-real-time-streaming-data-applications-with-cube"&gt;Register now&lt;/a&gt; to join us on October 13.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;Historically, it has been hard to work with truly real-time data. Specifically, it’s been challenging to combine historical and real-time data. For example, in use cases such as notifications or recommendations, we can’t rely only on last-minute data; we need to look back to run analysis, and then act in real-time, based on both the most recent data and historical trends.&lt;/p&gt;

&lt;p&gt;There’s also the concern of needing to master unique languages for specific data sources; in the past, it often was necessary to build one-off integrations for each streaming application to accommodate real-time streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  The opportunity
&lt;/h2&gt;

&lt;p&gt;Streaming data enables a variety of use cases, including real-time, in-product analytics; automation; personalization; and alerting. Fortunately, recent innovations have made it easier for us to accommodate real-time streams.&lt;/p&gt;

&lt;p&gt;In the last few years, streaming SQL technologies such as &lt;a href="https://ksqldb.io/"&gt;ksqlDB&lt;/a&gt;, &lt;a href="https://materialize.com/"&gt;Materialize&lt;/a&gt;, and &lt;a href="https://flink.apache.org/"&gt;Apache Flink&lt;/a&gt; have significantly progressed. These technologies enable us to process streaming data and run analysis with SQL—without needing to learn a new language or build specific language-unique integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  ksqlDB support
&lt;/h2&gt;

&lt;p&gt;We’re excited to announce that it’s now possible to use Cube to build data modeling, caching, and access control layers on top of streaming SQL, just as you can with data from cloud data warehouses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oTAXUDBW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tcwmtm1uhavwrsshta6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oTAXUDBW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tcwmtm1uhavwrsshta6i.png" alt="Cube schema with ksqlDB" width="880" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube now can connect to streaming SQL engines and expose streaming data via our REST, GraphQL, and SQL APIs to your downstream applications. Our REST API already supports websockets and provides a seamless experience for developers and data engineers to build real-time data apps. Our data modeling, caching, access control, and querying APIs are the same whether you work with streaming or batch data.&lt;/p&gt;

&lt;p&gt;Want to see it in action? &lt;a href="https://ksql-demo.netlify.app/"&gt;Check out our live ksqlDB demo application&lt;/a&gt; featuring a real-time dashboard built with Kafka, ksqlDB, and Cube.&lt;/p&gt;

&lt;p&gt;That alone opens many opportunities for building on top of streaming data, but we’re also committed to addressing the complex problem of merging batch and streaming data, so that we can provide a single interface for unioned data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda architecture support
&lt;/h2&gt;

&lt;p&gt;We’re also introducing &lt;a href="https://cube.dev/docs/caching/pre-aggregations/lambda-pre-aggregations"&gt;lambda pre-aggregations&lt;/a&gt;. These follow the &lt;a href="https://en.wikipedia.org/wiki/Lambda_architecture"&gt;lambda architecture&lt;/a&gt; design to create a union of streaming and batch data. Cube pre-aggregates batch data from the cloud data warehouse into one pre-aggregation, data from streaming SQL engines into another, then unites these pre-aggregations during query processing—so we can return merged data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h0oF00qs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iafzi2up66l650t6tvtv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h0oF00qs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iafzi2up66l650t6tvtv.png" alt="Cube schema with lambda architecture" width="880" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For developers or data consumers, the interface remains the same—REST, GraphQL, or SQL queries. Our Lambda architecture makes it possible to consume data as a single dataset regardless of whether it is streaming, batch, or combined data.&lt;/p&gt;

&lt;p&gt;Cube hides the complexity of this architecture within our data modeling layer, and therefore significantly simplifies the data consumption process. In addition, our access control and security layers are applied using the same rules as you’ve configured them for batched data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming next
&lt;/h2&gt;

&lt;p&gt;Naturally, our work isn’t done yet. The above features are generally available soon—and available today to Cube users who request access. Soon, you’ll also have access to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cube.dev/docs/config/databases/materialize"&gt;Materialize&lt;/a&gt; streaming pre-aggregations&lt;/li&gt;
&lt;li&gt;Flink SQL support&lt;/li&gt;
&lt;li&gt;Spark Streaming support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re interested in early access to these features, to help influence their development and craft the future of streaming headless BI, simply &lt;a href="https://cube.dev/contact"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everything you need to build powerful real-time applications&lt;br&gt;
We’re excited to launch our Kafka and ksqlDB integration and lambda pre-aggregations today because these bring the power and simplicity of our headless BI architecture to new types of data applications.&lt;/p&gt;

&lt;p&gt;These innovations are available to everyone, for free: &lt;a href="https://cube.dev/contact"&gt;request ksqlDB preview access&lt;/a&gt;, connect a real-time data source, then (please!) give us your feedback.&lt;/p&gt;

&lt;p&gt;We look forward to powering the next generation of data applications—we can’t wait to see what you build.&lt;/p&gt;

</description>
      <category>realtimedata</category>
      <category>kafka</category>
      <category>dataengineering</category>
      <category>analytics</category>
    </item>
    <item>
      <title>What is Headless BI?</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 24 Mar 2022 16:35:04 +0000</pubDate>
      <link>https://dev.to/keydunov/what-is-headless-bi-44hc</link>
      <guid>https://dev.to/keydunov/what-is-headless-bi-44hc</guid>
      <description>&lt;p&gt;With the &lt;a href="https://www.theregister.com/2021/11/15/rise_of_the_cloud_data_warehouse/"&gt;explosive growth of cloud data warehouses&lt;/a&gt;, an entire &lt;a href="https://www.moderndatastack.xyz"&gt;ecosystem of new data tools&lt;/a&gt; has emerged—transformation, testing, quality, observability, to name a few. On top of this modern data stack, we’ve seen a proliferation of new applications, including dashboards, &lt;a href="https://645ventures.com/voices/articles/the-commoditization-of-business-intelligence-tools-the-rise-of-embedded-intelligence-and-our-investment-in-cube-dev"&gt;embedded analytics&lt;/a&gt;, automation tools, and vertical-specific reporting tools. And, this boom in the modern data stack necessitates a new generation of business intelligence: headless BI.&lt;/p&gt;

&lt;p&gt;Why? Because with every addition to the ecosystem lies the challenge of defining metrics consistently, and making them accessible to every application. This challenge has only grown more urgent—as &lt;a href="https://benn.substack.com/p/metrics-layer?s=r"&gt;others&lt;/a&gt; &lt;a href="https://basecase.vc/blog/headless-bi"&gt;have&lt;/a&gt; &lt;a href="https://towardsdatascience.com/a-brief-history-of-the-metrics-store-28208ec8f6f1"&gt;observed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Specifically, the challenge of data consistency poses a problem for users of traditional business intelligence tools—like Tableau, Looker, or Mode. These tools take defining metrics upon themselves and don’t share their definitions with others:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While these tools enable business users to use their data better, they have one fatal constraint: users can only use the metrics they’ve defined within the four walls of the visualization tool.[&lt;a href="https://basecase.vc/blog/headless-bi"&gt;1&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In response, we’ve begun to see so-called &lt;a href="https://betterprogramming.pub/headless-bi-metric-standardization-in-action-afb2ac7e89b6"&gt;“headless” BI&lt;/a&gt;. These are platforms that decouple data metrics from the presentation layers that display them—pushing metrics definition up the data stack. &lt;/p&gt;

&lt;p&gt;However, the popularity of the phrase hasn’t been accompanied by a consistent definition of what headless BI even is. &lt;strong&gt;There is more to headless BI than just upstack metrics definition.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the benefit of data engineers and data consumers alike, we propose a standardized definition of headless business intelligence. And with it, we describe four essential components of a headless BI tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Data Modeling
&lt;/h2&gt;

&lt;p&gt;This is the most widely understood aspect of headless BI. Simply, software engineering best practices dictate that &lt;strong&gt;metrics and their governance should a) live in code, and b) be deduplicated.&lt;/strong&gt; Deduplication refers to removing them from each end-user application in which they’re variously and inconsistently defined, and moving them upstack.&lt;/p&gt;

&lt;p&gt;Fundamentally, defining metrics within a data application leads to duplicated effort and duplicated results. These redundancies—and, therefore, inconsistencies—occur because, regardless of its size, no organization only uses one data application. &lt;/p&gt;

&lt;p&gt;Sales teams use a CRM, marketing teams use marketing automation, and executives use dashboards. If each team were to independently count their own definition of “customer” or sum their own interpretation of “annual revenue”, at best these metrics would be defined more times than necessary. What’s more likely is that the definitions wouldn’t match—which is, in every case we can think of, not ideal. This outcome also defeats &lt;a href="https://www.mckinsey.com/business-functions/mckinsey-analytics/our-insights/the-data-driven-enterprise-of-2025"&gt;the whole point of collecting data and distributing it&lt;/a&gt; to every team.&lt;/p&gt;

&lt;p&gt;When a headless BI tool defines metrics used by every downstream application, companies get uniform insights instead of inconsistent signals. What’s more, headless BI can help organize and simplify once-complex SQL queries by abstracting them away: a metrics layer can generate SQL queries for defined metrics and dimensions, so downstream applications—and their users—don’t have to. This layer also plays an important role in data governance, including lifecycle management, ownership, and change approval.&lt;/p&gt;

&lt;p&gt;Lastly, in addition to its own definitions, a headless BI platform should consume those from further upstream—&lt;a href="https://cube.dev/blog/dbt-metrics-meet-cube/"&gt;e.g., those from transformation tools like dbt.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Access Control
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Since a headless BI tool is the first layer downstream of one’s data sources, it must also manage and audit access to that data.&lt;/strong&gt; These controls can’t be decoupled from the data model, and, therefore, should be defined within it.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;First, this need &lt;em&gt;also&lt;/em&gt; stems from a goal of deduplication. As matters of both good architecture and security best practices, the rules of who can access what should be defined and enforced once, not redundantly—and possibly inconsistently—by downstream tools.&lt;/p&gt;

&lt;p&gt;Second, by instrumenting access control within the data model, you are able to use dynamic metrics definitions. That is, metrics definitions that vary based on the context in which they’re requested. Contextually-informed metrics are particularly useful in a multi-tenant model in which (e.g.) each of your customers ought to access a defined measure, “sales”, that reflects their revenue. Each data consumer gains access only to the subset of metrics to which they’re entitled—without manually redefining “sales” for each.&lt;/p&gt;

&lt;p&gt;Implementing access control within a headless BI tool enables making aggregate metrics widely accessible. Additionally, doing so also protects specific sensitive data (e.g., personally identifiable information.) With the precise ability to control access, you can limit it only to an authorized subset of individuals or applications.&lt;/p&gt;

&lt;p&gt;The above two layers constitute what could be considered a “metrics store” or a “modeling layer.” But we’ll continue with our assertion and say: they are not sufficient to make up a headless BI solution.&lt;/p&gt;

&lt;p&gt;To constitute headless BI, you also need to make data readily accessible to data consumers, and for that you need…&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Caching
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A headless BI tool is the correct place in which to situate a data caching layer.&lt;/strong&gt; At first, it may seem counterintuitive to locate a caching layer up-stack of data applications. However, the guiding principles here are, once again, consistency and deduplication.&lt;/p&gt;

&lt;p&gt;For starters, a centrally located caching layer ensures consistent data freshness across tools, using one centrally defined schedule of cache invalidation and warming. Every tool will present the same values at the same time.&lt;/p&gt;

&lt;p&gt;Additionally, if a headless BI tool has been tasked with accessing data from a data store and organizing it into definitions, then it is optimally positioned to manage queues of requests from multiple applications and data consumers. &lt;/p&gt;

&lt;p&gt;Without this mediation, multiple applications may make the same requests. Redundant requests both incur needless bandwidth costs and degrade the performance of other concurrent queries.&lt;/p&gt;

&lt;p&gt;Headless BI can provide several levels of caching. First, a query-level cache can store the results of the query to ensure that identical queries from a tool, or multiple tools, do not increase the load on the underlying data warehouse. &lt;/p&gt;

&lt;p&gt;Additionally, a second layer can implement aggregate awareness: logic to find the smallest, most efficient table to serve the query. Aggregate tables can be either created externally or within the headless BI tool, and can significantly speed up queries and solve the cold cache problem when maintained in the background.&lt;/p&gt;

&lt;p&gt;The prior era of multi-second dashboard loads is over, and it’s not coming back. By managing caching, a headless BI tool ensures sub-second responses in every downstream application, regardless of the number of requests or the volume of data in the underlying data store.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. APIs
&lt;/h2&gt;

&lt;p&gt;Finally, we get to the essential nature of headless BI’s “headlessness.” &lt;strong&gt;A headless BI tool must make its data accessible to every “head” application, be it data visualization, dashboarding, embedded analytics, or automation.&lt;/strong&gt; This means that a headless BI tool must make its data available via various APIs.&lt;/p&gt;

&lt;p&gt;The most obvious candidate here is a SQL endpoint. This enables data consumers to keep using dashboard tools, notebooks, and legacy applications, such as Tableau, that they formerly would have directly connected to a data warehouse.&lt;/p&gt;

&lt;p&gt;The next additions are REST and GraphQL. These ensure that the same metrics that reach dashboards are also available to embedded analytics features, end-user-facing applications, and other innovative uses of your data.&lt;/p&gt;

&lt;p&gt;By exposing APIs to connect discrete applications, a headless BI tool makes it possible to build new automations. Some examples include a workflow that alerts a sales team to contact a customer based on changes in her account usage, or an upstream data source for applications such as reverse ETL. &lt;/p&gt;

&lt;p&gt;“Business intelligence” no longer solely consists of reacting to data in a dashboard; now, an intelligent business actually has actionable data—and acts on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless BI should be open source
&lt;/h2&gt;

&lt;p&gt;We’ve just laid out the essential components of a headless BI system. In theory, this could all be furnished by a vendor in a vertically-integrated closed system. However, &lt;strong&gt;a headless BI system is truly valuable when it integrates into any data stack&lt;/strong&gt;, made up of pieces from any number of vendors.&lt;/p&gt;

&lt;p&gt;As a practical matter, this is only possible when multiple companies collaborate: each vendor can contribute connectors and optimizations to improve the compatibility of a headless BI tool with their tools.&lt;/p&gt;

&lt;p&gt;Furthermore, headless BI is so important, and its benefits to the community are so great, that &lt;strong&gt;this technology must be made available to everyone.&lt;/strong&gt; Its stewardship should be guided by community input and community values.&lt;/p&gt;

&lt;p&gt;We’re proud of the community that has grown around Cube in the &lt;a href="https://github.com/cube-js/cube.js/releases/tag/v0.3.5-alpha.0"&gt;three years since we open-sourced our headless BI platform&lt;/a&gt;. Over 200 contributors have enriched our tool on &lt;a href="https://github.com/cube-js/cube.js"&gt;our GitHub repo&lt;/a&gt;, and over 5000 users have joined our community Slack to trade tips and share feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless BI: WIP
&lt;/h2&gt;

&lt;p&gt;What next? There are many optimizations to add, tools with which to integrate, and powerful features to build. Along the way, we’re &lt;a href="https://github.com/cube-js/cube.js/issues/3906"&gt;soliciting and building connectors&lt;/a&gt; to tools like legacy BI applications and gathering feedback in &lt;a href="https://slack.cube.dev"&gt;our Slack&lt;/a&gt;. Our &lt;a href="https://twitter.com/messages/compose?recipient_id=179985200"&gt;Twitter DMs&lt;/a&gt; are open and &lt;a href="http://cube.dev/community-call"&gt;our community meets&lt;/a&gt; every month. And, with this momentum, &lt;a href="http://cube.dev/careers/"&gt;we’re hiring&lt;/a&gt; more bright minds to help build Cube full-time. &lt;/p&gt;

&lt;p&gt;Together, we’ll bring data to life and power the next generation of consistent and powerful data applications.&lt;/p&gt;

&lt;p&gt;Won’t you join us?&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>database</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Building Open-Source Metrics Stores with Cube</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 18 Nov 2021 15:51:30 +0000</pubDate>
      <link>https://dev.to/cubejs/building-open-source-metrics-stores-with-cube-3fpj</link>
      <guid>https://dev.to/cubejs/building-open-source-metrics-stores-with-cube-3fpj</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: You can build a consistent and reusable metrics layer and connect it to a front-end app and a BI tool like Superset or Tableau at the same time.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today, &lt;a href="http://github.com/cube-js/cube.js"&gt;Cube&lt;/a&gt; powers analytics features inside thousands of applications where developers have leveraged Cube’s data schema as a &lt;strong&gt;metrics layer&lt;/strong&gt;—a consistent, single source of truth. By powering a single repository of metrics, Cube helps developers quickly and reliably ship embedded analytics features and other data-powered applications with the peace of mind that their metrics definitions remain consistent.&lt;/p&gt;

&lt;p&gt;Cube solves this problem very well for application developers, but it’s been unsolved for users of dashboards and business intelligence tools, and the data engineering teams that support them.&lt;/p&gt;

&lt;p&gt;In most modern organizations, there are multiple tools that consume data from the same single warehouse but perform their own downstream metrics calculations. This leads to inconsistent calculations and disagreements between teams: &lt;strong&gt;How do we calculate revenue? Does it include revenue from X cohort? How do monthly payments affect annual projections?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The more tools an organization uses, the more inconsistencies and conflicts arise, and the harder it is for a business to make decisions using accurate data. To avoid this, they need a centralized, upstream location in which to build and maintain their metrics, so that every tool works from the same source of metric truth.&lt;/p&gt;

&lt;p&gt;Today, I’m excited to share a way for Cube to function as a metrics store for any data consumer: we’re proud to announce the new &lt;a href="https://cube.dev/docs/backend/sql/?ref=introducing-cube-sql"&gt;Cube SQL API&lt;/a&gt;. With the addition of this API, Cube now functions as a headless BI layer to provide consistent metrics to any querying and visualization tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z5AB6Qx4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/ebff03e0-fbcd-4941-9a3c-cc71b6142561.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z5AB6Qx4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/ebff03e0-fbcd-4941-9a3c-cc71b6142561.png" alt="data-flow-diagram" width="880" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;In the modern data stack, Cube acts as a proxy for data warehouses and translates every incoming query, whether it is JSON, GraphQL, or SQL, into native queries to the underlying data storage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dTC6QK8J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e47e56c8-07ee-4330-8b6a-e899244cbac1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dTC6QK8J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e47e56c8-07ee-4330-8b6a-e899244cbac1.png" alt="sql-generation" width="880" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube translates queries by using a JSON-based data modeling layer consisting of cubes. Think of these as database views, backed by either a reference to an existing database table or a new table created by SELECT statement. Cubes contain defined measures and dimensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measures&lt;/strong&gt; are quantitative data, such as the number of units sold, unique visits, profit, and so forth. &lt;strong&gt;Dimensions&lt;/strong&gt; are categorical data, such as state, gender, product name, or units of time. (You can learn more about Cube’s &lt;a href="https://cube.dev/docs/schema/getting-started?ref=introducing-cube-sql"&gt;data schema&lt;/a&gt; in the docs.)&lt;/p&gt;

&lt;p&gt;Below is an example of the cube we can use to describe metrics about leads for a sales organization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Leads`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    SELECT
      persons.id,
      persons.created_date,
      deals.id as deal_id
    FROM persons
      LEFT JOIN deals ON deals.person_id = persons.id
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Leads`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Leads for Insides Sales`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;converted_to_deal_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;is_converted_to_deal&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) = true`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;to_deal_conversion_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`ROUND(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;converted_to_deal_count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; / NULLIF(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, 0) * 100, 2)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`percent`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`created`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;is_converted_to_deal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`boolean`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`deal_id IS NOT NULL`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the above definitions of our metrics we can ask questions like &lt;strong&gt;"What is our monthly lead-to-deal conversion rate over the last year?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When we query Cube’s metrics layer via the SQL API, cubes will be presented as tables and measures and dimensions as columns. To answer the above question with SQL we need to write the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;DATE_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="n"&gt;to_deal_conversion_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;leads&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2021-01-01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="s1"&gt;'2021-12-31'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cube will translate this query into the query for the underlying database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;DATETIME_TRUNC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;DATETIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'America/Los_Angeles'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;MONTH&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;`leads__time_month`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;deal_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;`leads__to_deal_conversion_rate`&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt;
      &lt;span class="n"&gt;persons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;persons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;deals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;deal_id&lt;/span&gt;
     &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;persons&lt;/span&gt;
      &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;deals&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;deals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;persons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`leads`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;created_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2021-01-01T00:00:00.000Z'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2021-12-31T23:59:99.999Z'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;
  &lt;span class="mi"&gt;10000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can query Cube with SQL from your favorite programming language, such as Python.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sXMGIGQv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/2b0d23c8-37fa-4550-8c99-53196c832a26.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sXMGIGQv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/2b0d23c8-37fa-4550-8c99-53196c832a26.gif" alt="jupiter" width="880" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most powerfully, because the Cube SQL API speaks MySQL-compatible SQL, you also can connect your favorite BI tool—like Superset, Metabase, or Tableau—directly to Cube, and let Cube generate SQL to fetch and display data. Here’s an example of using Superset with the Cube SQL API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ivv2odPJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a775a871-8e89-451c-afb1-671b6dd22e3f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ivv2odPJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a775a871-8e89-451c-afb1-671b6dd22e3f.png" alt="superset" width="880" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check our documentation for the full &lt;a href="https://cube.dev/docs/recipes/using-apache-superset-with-cube-sql?ref=introducing-cube-sql"&gt;Superset integration tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in relational cache
&lt;/h2&gt;

&lt;p&gt;Every query can leverage Cube’s pre-aggregations layer—a database-agnostic materialization engine—in order to make dashboards and reports load in milliseconds instead of minutes.&lt;/p&gt;

&lt;p&gt;While defining metrics, developers can specify which metrics they want to pre-aggregate. Cube will run complex calculations beforehand in the background and will create a cache table with the results. All queries will be served from this cache table, dramatically increasing dashboards’ performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we go next
&lt;/h2&gt;

&lt;p&gt;We’re excited about today’s launch, but it is only the beginning of the journey!  We’re committed to supporting all the major SQL operations, and improving error handling to provide more useful tips to users. Please let us know in our Slack channel if you see something that is not working as you expected.&lt;/p&gt;

&lt;p&gt;Everything described here is available in Cube’s OSS offering, licensed under Apache 2.0. In addition, we're working on cataloguing and collaboration tools for the Cube metrics layer in Cube Cloud, our fully-managed Cube service. Watch this space.&lt;/p&gt;

&lt;p&gt;If you’re interested in learning more, &lt;a href="https://cube.dev?ref=introducing-cube-sql"&gt;please give Cube a try&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Announcing Cube Cloud: Managed hosting of Cube applications</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Tue, 05 Oct 2021 13:55:43 +0000</pubDate>
      <link>https://dev.to/cubejs/announcing-cube-cloud-managed-hosting-of-cube-applications-342e</link>
      <guid>https://dev.to/cubejs/announcing-cube-cloud-managed-hosting-of-cube-applications-342e</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: Cube is an open-source API layer for building modern data apps. Here's a &lt;a href="https://dev.to/cubejs/building-clickhouse-dashboard-and-crunching-wallstreetbets-data-14ao"&gt;step-by-step tutorial&lt;/a&gt; that shows how Cube shines. Now Cube can be run on a fully managed platform — Cube Cloud. It's a small step for the Cube team, but a giant leap for the developers: Cube Cloud has a free tier so everyone can start building with Cube right away. Read more below...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today, we’re excited to announce general availability of Cube Cloud, a fully managed service for running Cube applications in the cloud of your choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can try it out for free at &lt;a href="https://cube.dev/cloud" rel="noopener noreferrer"&gt;cube.dev/cloud&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The launch follows more than two years of collecting community feedback about running Cube in production, which started with my cofounder Pavel and me helping community members manage Cube under load. We learned a lot about different load footprints, use cases, and patterns of how to scale Cube to match different use cases, and began work on what later became Cube Cloud.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Our mission:&lt;/strong&gt; to help developers focus on building data applications with Cube, not on running, monitoring, and scaling Cube infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Our plan:&lt;/strong&gt; a platform purpose-built to run Cube applications, smart enough to autoscale to accommodate usage growth, and rich with controls to trace queries, inspect load, and work with Cube pre-aggregations.&lt;/p&gt;

&lt;p&gt;I’m excited to share what we’ve built. Let’s take a look at some important new features:&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-scale Cube API instances
&lt;/h2&gt;

&lt;p&gt;Cube API instances are designed to be scaled horizontally, but it’s hard to say at what CPU, memory, or requests threshold you need to scale, since this depends on a variety of factors, including your schema structure and footprint, API workload patterns, and database resources.&lt;/p&gt;

&lt;p&gt;In Cube Cloud, you don't need to worry about any of it—how many instances to run, how much memory to allocate, etc. Cube Cloud knows how to autoscale your deployment based on API workloads and a multi-tenancy structure to load-balance schemas per API instance.&lt;/p&gt;

&lt;p&gt;Cube Cloud also auto-scales pre-aggregations of the Cube relational caching layer. Auto-scaling ingestion helps to keep cache refresh time constant even when the underlying data grows, and the same is true for the querying side: Cube Cloud takes care of bringing more Cube Store workers to process all the incoming requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F9c62128c-195a-4ddf-b887-f63e61eb2033.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F9c62128c-195a-4ddf-b887-f63e61eb2033.png" alt="autoscale.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache and pre-aggregations warm-up
&lt;/h2&gt;

&lt;p&gt;To ensure a good user experience, we recommend pre-compiling your schema and warming up both in-memory and pre-aggregations caches before serving user requests. It usually happens during the rollout of updates to the data schema. When building multitenant applications and load balancing different schema versions across API instances, it is important to pre-compile versions of the schema per serving API instance, especially for dynamic schemas that make third-party requests.&lt;/p&gt;

&lt;p&gt;Cube Cloud takes care of schema pre-compilations and cache warm-ups for you, including balancing multi-tenant schema versions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F148c34dd-1367-4e35-8d28-d5936f23dcc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F148c34dd-1367-4e35-8d28-d5936f23dcc7.png" alt="multi-tenant-schema.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub integration and collaborative schema editing
&lt;/h2&gt;

&lt;p&gt;Cube Cloud integrates with GitHub to streamline the process of developing and testing schema edits with your team.&lt;/p&gt;

&lt;p&gt;First, Cube Cloud can automatically deploy changes from a connected GitHub repository. Simply commit and git push to trigger a deployment.&lt;/p&gt;

&lt;p&gt;Additionally, if you want to edit and test your schema directly in Cube Cloud, you can do so via Development Mode, which deploys your own personal API endpoint to test out via Playground as you edit the schema. When you’re happy with your updates, you can commit and push your changes via the git integration, then share these updates with the rest of your team or push them to a production environment in Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F7da053d1-21d4-46e1-bb63-372ef3e08eed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F7da053d1-21d4-46e1-bb63-372ef3e08eed.png" alt="schema_editing.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’re now actively working on some neat features to support staging environments and more collaborative workflows. For example, multiple branches on each deployment will soon allow you to set up multiple environments with the same backend data source and caches, while using different schema versions and resource configurations. We’ll also enable you to open pull requests and merge branches directly from Cube Cloud. Watch this space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;Cube Cloud provides performance monitoring for API instances and your source database. Use this tool to monitor overall load, and spot slow queries to optimize.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F6f0b86f3-6952-4460-aa82-168b306c2e97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F6f0b86f3-6952-4460-aa82-168b306c2e97.png" alt="monitoring.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, we're working on integrations with third-party alerting and APM tools like Datadog and PagerDuty, so you can export metrics and logs of your Cube Cloud deployment to these tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query Tracing
&lt;/h2&gt;

&lt;p&gt;Query tracing provides a historical view of all your Cube API and database queries. This can be very helpful both during development and in production to find slow queries, understand the lifecycle of each query, and optimize performance and caching configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F2c985fed-e80c-4a70-be8a-9f43bfb42372.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F2c985fed-e80c-4a70-be8a-9f43bfb42372.png" alt="query_tracing.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-aggregations management
&lt;/h2&gt;

&lt;p&gt;Cube can accelerate a query by utilizing pre-aggregations—aggregated and optimized representations of source data—instead of processing raw data. In Cube Cloud, it’s easier to create and configure pre-aggregations during development and maintain them in production. We've built a control panel to view all your pre-aggregations in one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F6b07ade3-5246-40cc-a033-47c94389bcde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F6b07ade3-5246-40cc-a033-47c94389bcde.png" alt="pre-aggregations.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tool lets you view build history, partitions, build times, refresh times, and much more. You can also build pre-aggregations manually with the &lt;code&gt;Build All&lt;/code&gt; button, or select individual partitions to build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fdb60d2cd-d8ff-45a3-8891-49d6729627ad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fdb60d2cd-d8ff-45a3-8891-49d6729627ad.png" alt="pre-aggregations-3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To track the history of pre-aggregations over time, Cube Cloud provides a list of every build that happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F45bac091-e3f4-4dc8-b235-f40f7724e3df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F45bac091-e3f4-4dc8-b235-f40f7724e3df.png" alt="pre-aggregations-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;Cube Cloud is not just the fastest way to run Cube; we aim for it to be the most secure.&lt;/p&gt;

&lt;p&gt;For starters, all communications between Cube Cloud and your databases can be encrypted. In addition, it’s possible to run Cube Cloud in an isolated, dedicated VPC that can be peered to your database's VPC.&lt;/p&gt;

&lt;p&gt;Encryption at rest for pre-aggregations using Customer Key Management is available for dedicated VPC instances.&lt;/p&gt;

&lt;p&gt;To make it easier to use Cube Cloud in large enterprise environments, we’re actively in the process of earning our SOC-2 certification. If you’d like to be updated on our progress with this certification, please let us know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting price: $0
&lt;/h2&gt;

&lt;p&gt;We’re so confident that Cube Cloud is the superior way to develop and run Cube, and proud of the features that we’ve added, that we encourage every developer to use it.&lt;/p&gt;

&lt;p&gt;Cube Cloud is totally free for up to 1GB/month of processed data—with no limitations on access to the Cube Cloud IDE and no cap on the number of data stores you connect or API instances you create. &lt;a href="https://cubecloud.dev/auth/signup" rel="noopener noreferrer"&gt;Give it a try now&lt;/a&gt;. (For additional data as well as auto-scaling and high availability, pricing starts at just $99/month. &lt;a href="https://cube.dev/pricing" rel="noopener noreferrer"&gt;Learn more about Cube Cloud pricing here&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;If your team requires dedicated infrastructure, support and training, SLAs, or other custom enterprise features, &lt;a href="https://cube.dev/contact" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;: we’re happy to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is just the beginning
&lt;/h2&gt;

&lt;p&gt;More is definitely in store! We will continue to build more tools in Cube Cloud to ensure smooth development, staging, and production for Cube-powered data applications. We also hope you’ll share your feedback and feature requests by &lt;a href="//mailto:hello@cube.dev"&gt;dropping us an email&lt;/a&gt;, &lt;a href="https://twitter.com/thecubejs" rel="noopener noreferrer"&gt;finding us on Twitter&lt;/a&gt;, or &lt;a href="https://slack.cube.dev" rel="noopener noreferrer"&gt;participating in our Slack community&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We raised Series A to build headless data analytics</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Mon, 19 Jul 2021 15:14:17 +0000</pubDate>
      <link>https://dev.to/cubejs/we-raised-series-a-to-build-headless-data-analytics-2kk</link>
      <guid>https://dev.to/cubejs/we-raised-series-a-to-build-headless-data-analytics-2kk</guid>
      <description>&lt;p&gt;Less than a year ago, I was writing a blog post announcing our seed fundraising. I didn't expect to write a successor post so soon! But today we are overjoyed to announce that Cube Dev has raised $15.5M in Series A funding, led by Decibel.&lt;/p&gt;

&lt;p&gt;It’s been a busy eleven months. Since last August, there are 5 times more servers running Cube, and as many new members in our Slack community. The past months have also seen the rollout of powerful new features including &lt;a href="https://cube.dev/blog/introducing-cubestore/"&gt;Cube Store&lt;/a&gt;, and the emergence of new community features and educational resources.&lt;/p&gt;

&lt;p&gt;In announcing this funding round, I want to share what we’ve learned over that time, and explain how we will use the new funding and what we’ve learned to more quickly build the tech stack that powers data applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new architecture for data applications
&lt;/h2&gt;

&lt;p&gt;The database and cloud data warehouse industry is booming. We indeed have more data than ever. From the Cube community, I know a lot of seed-stage startups dealing with hundreds of millions and even billions of records.&lt;/p&gt;

&lt;p&gt;The good news is that the technology now exists to process all of this data. Remember that less than a decade ago, only a privileged few big tech firms could afford the budget needed to run and manage Hadoop clusters on a scale, but since then, cloud data warehouses emerged as Ford's Model T for data. Now, even small companies can afford to store and analyze big data. This enables everyone to build data applications and embed intelligence into their products.&lt;/p&gt;

&lt;p&gt;That being said, we still have to build fundamental infrastructure to make it possible—to bring data to life inside the applications. If data is the new oil, we need the tools to refine it into rocket fuel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ic7lMxwb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/02311426-3a75-4c2f-a128-72f1cc320f4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ic7lMxwb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/02311426-3a75-4c2f-a128-72f1cc320f4e.png" alt="Cube scheme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Developers need a new architecture to deliver data from warehouses and data lakes to applications at  scale, and there are hard problems to solve in designing this architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we abstract metrics definitions and make them consistent, testable, and version controllable?&lt;/li&gt;
&lt;li&gt;How do we ensure that our API layer supports a large number of users and high volumes of data without growing in complexity?&lt;/li&gt;
&lt;li&gt;How do we manage complex multi-tenancy scenarios and per-end-user custom metrics?&lt;/li&gt;
&lt;li&gt;How do we aggregate and cache the data to process hundreds and even thousands of queries per second?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ve spent more than two years tackling these questions with all of you in our amazing Cube community, and we’ve all learned a lot.&lt;/p&gt;

&lt;p&gt;Over two years ago, when Pavel and I were playing ping-pong and chatting about open-sourcing Cube.js, I couldn't have imagined where it would lead us. Today’s Cube is so different from what we initially released. Community not only contributed features but pioneered many fundamental architecture decisions we have now in the Cube.&lt;/p&gt;

&lt;p&gt;Now, with new funding, we're excited to start a new chapter and to build more and faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we go from here
&lt;/h2&gt;

&lt;p&gt;Cube’s mission is to &lt;strong&gt;empower developers to build data applications&lt;/strong&gt;. To pursue this mission, here is some of what we have planned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hiring.&lt;/strong&gt; We plan to build a team of 30 people by the end of the year and double our open-source engineering team. This allows us to fix bugs and ship new features faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend Cube Store.&lt;/strong&gt; It's been less than 3 months since we released Cube Store, but it's already been adopted by more than half of Cube users. As many of you have noticed, Cube Store provides a significant boost for latency and concurrency on analytics queries like Top-K. We intend to further improve its performance and the ease of configuring and deploying Cube Store and work to make its immense power accessible to every user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New features.&lt;/strong&gt; I always enjoy hours spent chatting with the community about what we need to build next. There are many things I know you are excited about. Two frequent requests are &lt;strong&gt;real-time pre-aggregations from Kafka streams and databases&lt;/strong&gt;, and &lt;strong&gt;BI connectors&lt;/strong&gt;. I'm happy to share that we've already started to work on these, and plan to ship previews later this year. Please DM me on Slack if you are interested in running Cube on top of real-time and streaming data, or connecting Cube to your BI tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Launch Cube Cloud.&lt;/strong&gt; We're also hard at work building Cube Cloud, our fully managed Cube service coming later this year too. Managing infrastructure opens so many new opportunities to build features we’ve always wanted to add to Cube but couldn’t without access to the underlying infrastructure. I can't wait to share it with you all. Cube Cloud will launch to general availability in the autumn, but first we’re working with design partners who can test Cube Cloud with their data, influence feature development, and work hands-on with the Cube Dev team in advance of general availability. Can you help? Don’t miss this opportunity to shape Cube’s next chapter. &lt;a href="https://cube.dev/cloud/"&gt;Drop us a line&lt;/a&gt; to learn more.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Building an Open Source Web Analytics Platform. Part 1: Overview and Analytics Backend
</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 09 Jul 2020 14:43:39 +0000</pubDate>
      <link>https://dev.to/keydunov/building-an-open-source-web-analytics-platform-part-1-overview-and-analytics-backend-4kdf</link>
      <guid>https://dev.to/keydunov/building-an-open-source-web-analytics-platform-part-1-overview-and-analytics-backend-4kdf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first part of a guide on building an open source web analytics platform with Cube.js You can find &lt;a href="https://web-analytics.cube.dev/" rel="noopener noreferrer"&gt;the full guide here.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Also, below you can see the demo of the final application. Online demo is &lt;a href="https://web-analytics-demo.cubecloudapp.dev" rel="noopener noreferrer"&gt;availble here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/hJPf-KeYmJE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Building your own analytics engine, like the one behind Google Analytics, sounds like a very sophisticated engineering problem. And it truly is. Back then, it would require years of engineering time to ship such a piece of software. But as data landscape changes, now we have a lot of tools which solve different part of this problem extremely well: data collection, storage, aggregations, and query engine. By breaking the problem into smaller pieces and solving them one-by-one by using existing open-source tools, we will be able to build our own web analytics engine.&lt;/p&gt;

&lt;p&gt;If you’re familiar with Google Analytics (GA), you probably already know that every web page tracked by GA contains a GA tracking code. It loads an async script that assigns a tracking cookie to a user if it isn’t set yet. It also sends an XHR for every user interaction, like a page load. These XHR requests are then processed, and raw event data is stored and scheduled for aggregation processing. Depending on the total amount of incoming requests, the data will also be sampled.&lt;/p&gt;

&lt;p&gt;Even though this is a high-level overview of Google Analytics essentials, it’s enough to reproduce most of the functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;Below you can see the architecture of the application we are going to build. We'll use Snowplow for data collection, Athena as the main data warehouse, MySQL to store pre-aggregations, and Cube.js as the aggregation and querying engine. The frontend will be built with React, Material UI, and Recharts. Although the schema below shows some AWS services, they can be partially or fully substituted by open-source alternatives: Kafka, MinIO, and PrestoDB instead of Kinesis, S3, and Athena, respectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcube-js%2Fcube.js%2Fmaster%2Fexamples%2Fweb-analytics%2Fweb-analytics-schema.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcube-js%2Fcube.js%2Fmaster%2Fexamples%2Fweb-analytics%2Fweb-analytics-schema.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll start with data collection and gradually build the whole application, including the frontend. If you have any questions while going through this guide, please feel free to join this Slack community and post your question there.&lt;/p&gt;

&lt;p&gt;We're going to use Snowplow for data collection, S3 for storage, and Athena to query the data in S3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Collection with Snowplow
&lt;/h2&gt;

&lt;p&gt;Snowplow is an analytics platform to collect, enrich, and store data. We'll use the Snowplow Javascript tracker on our website, which generates event-data and send it to the Snowplow Collector to load to S3.&lt;/p&gt;

&lt;p&gt;Before loading the data, we'll use Enricher to turn IP addresses into coordinates. We'll use AWS Kinesis to manage data streams for collection, enrichment, and then finally loading into S3. The schema below illustrates the whole process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fweb-analytics.cube.dev%2Fimages%2F2-schema-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fweb-analytics.cube.dev%2Fimages%2F2-schema-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start by setting up the tracker. Adding Snowplow's tracker to the website is the same, as adding Google Analytics or Mixpanel tracker. You need to add the asynchronous Javascript code, which loads the tracker itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;Snowplow&lt;/span&gt; &lt;span class="nx"&gt;starts&lt;/span&gt; &lt;span class="nx"&gt;plowing&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/javascript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]){&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GlobalSnowplowNamespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GlobalSnowplowNamespace&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GlobalSnowplowNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)}}(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;//d1fc8wv8zag5ca.cloudfront.net/2.10.2/sp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;snowplow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snowplow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newTracker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{MY-COLLECTOR-URI}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Initialise a tracker&lt;/span&gt;
  &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{MY-SITE-ID}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cookieDomain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{{MY-COOKIE-DOMAIN}}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snowplow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trackPageView&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;Snowplow&lt;/span&gt; &lt;span class="nx"&gt;stops&lt;/span&gt; &lt;span class="nx"&gt;plowing&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above snippet references a Snowplow Analytics hosted version of the Snowplow JavaScript tracker v2.10.2 (//d1fc8wv8zag5ca.cloudfront.net/2.10.2/sp.js). Snowplow Analytics no longer hosts the latest versions of the Snowplow JavaScript tracker. It is recommended to self-host &lt;code&gt;sp.js&lt;/code&gt; by following the &lt;a href="https://github.com/snowplow/snowplow/wiki/self-hosting-snowplow-js" rel="noopener noreferrer"&gt;Self-hosting Snowplow.js&lt;/a&gt; guide.&lt;/p&gt;

&lt;p&gt;For more details about setting up the tracker, please refer to the &lt;a href="https://github.com/snowplow/snowplow/wiki/Setting-up-a-Tracker" rel="noopener noreferrer"&gt;official Snowplow Javascript Tracker Setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To collect the data from the tracker, we need to setup Snowplow Collector. We'll use Scala Stream Collector. Here the detailed guide on how to install and configure it. This repository with the Docker images for the Snowplow components is very helpful if you plan to deploy Snowplow with Docker.&lt;/p&gt;

&lt;p&gt;Next, we need to install Snowplow Stream Enrich. Same as for collector, I&lt;br&gt;
recommend following the official guide here and use these Docker images.&lt;/p&gt;

&lt;p&gt;Finally, we need to have S3 Loader installed and configured to consume records from AWS Kinesis and writes them to S3. You can follow [this guide (&lt;a href="https://github.com/snowplow/snowplow/wiki/snowplow-s3-loader-setup" rel="noopener noreferrer"&gt;https://github.com/snowplow/snowplow/wiki/snowplow-s3-loader-setup&lt;/a&gt;) to set it up it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Query S3 with Athena
&lt;/h2&gt;

&lt;p&gt;Once we have data in S3 we can query it with AWS Athena or Presto. We’ll use Athena in our guide, but you can easily find a lot of materials online on how to set up an alternative configuration.&lt;/p&gt;

&lt;p&gt;To query S3 data with Athena, we need to create a table for Snowplow events. Copy and paste the following DDL statement into the Athena console. Modify the LOCATION for the S3 bucket that stores your enriched Snowplow events.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;snowplow_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;app_id&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;etl_tstamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;collector_tstamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dvce_tstamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;txn_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name_tracker&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;v_tracker&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;v_collector&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;v_etl&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_ipaddress&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_fingerprint&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;domain_userid&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;domain_sessionidx&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;network_userid&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_country&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_region&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_city&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_zipcode&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_latitude&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_longitude&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_region_name&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ip_isp&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ip_organization&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ip_domain&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ip_netspeed&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_url&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_title&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_referrer&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_urlscheme&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_urlhost&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_urlport&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_urlpath&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_urlquery&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;page_urlfragment&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_urlscheme&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_urlhost&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_urlport&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_urlpath&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_urlquery&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_urlfragment&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_medium&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_source&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_term&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;mkt_medium&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;mkt_source&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;mkt_term&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;mkt_content&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;mkt_campaign&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;contexts&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;se_category&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;se_action&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;se_label&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;se_property&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;se_value&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;unstruct_event&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_orderid&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_affiliation&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_total&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_tax&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_shipping&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_city&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_state&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_country&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_orderid&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_sku&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_name&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_category&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_price&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_quantity&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pp_xoffset_min&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pp_xoffset_max&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pp_yoffset_min&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pp_yoffset_max&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;useragent&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_name&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_family&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_version&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_type&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_renderengine&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_lang&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_pdf&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_flash&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_java&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_director&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_quicktime&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_realplayer&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_windowsmedia&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_gears&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_features_silverlight&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_cookies&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_colordepth&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_viewwidth&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;br_viewheight&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;os_name&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;os_family&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;os_manufacturer&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;os_timezone&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dvce_type&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dvce_ismobile&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dvce_screenwidth&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dvce_screenheight&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;doc_charset&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;doc_width&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;doc_height&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_currency&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_total_base&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_tax_base&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tr_shipping_base&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_currency&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ti_price_base&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;base_currency&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;geo_timezone&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;mkt_clickid&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;mkt_network&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;etl_tags&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dvce_sent_tstamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_domain_userid&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;refr_dvce_tstamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;derived_contexts&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;domain_sessionid&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;derived_tstamp&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;PARTITIONED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="n"&gt;FORMAT&lt;/span&gt; &lt;span class="n"&gt;DELIMITED&lt;/span&gt;
&lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;TERMINATED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;t'&lt;/span&gt;
&lt;span class="n"&gt;STORED&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TEXTFILE&lt;/span&gt;
&lt;span class="k"&gt;LOCATION&lt;/span&gt; &lt;span class="s1"&gt;'s3://bucket-name/path/to/enriched/good'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we're ready to connect Cube.js to Athena and start building our application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics API with Cube.js
&lt;/h2&gt;

&lt;p&gt;We'll build our analytics API on top of the Athena with &lt;a href="https://github.com/cube-js/cube.js" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt;. Cube.js is an open-source framework for building analytical web applications. It creates an analytics API on top of the database and handles things like SQL organization, caching, security, authentication, and much more.&lt;/p&gt;

&lt;p&gt;Let's install Cube.js CLI and use it to create our application. Run the following commands in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; cubejs-cli
&lt;span class="nv"&gt;$ &lt;/span&gt;cubejs create react-dashboard &lt;span class="nt"&gt;-d&lt;/span&gt; athena
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once run, the &lt;code&gt;create&lt;/code&gt; command will create a new project directory that contains the scaffolding for your new Cube.js project. Cube.js uses environment variables starting with CUBEJS_ for configuration. To configure the connection to Athena, we need to specify the AWS access and secret keys with the access necessary to run Athena queries, and the target AWS region and S3 output location where query results are stored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CUBEJS_DB_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athena
&lt;span class="nv"&gt;CUBEJS_AWS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR ATHENA AWS KEY HERE&amp;gt;
&lt;span class="nv"&gt;CUBEJS_AWS_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR ATHENA SECRET KEY HERE&amp;gt;
&lt;span class="nv"&gt;CUBEJS_AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;AWS REGION STRING, e.g. us-east-1&amp;gt;
&lt;span class="c"&gt;# You can find the Athena S3 Output location here: https://docs.aws.amazon.com/athena/latest/ug/querying.html&lt;/span&gt;
&lt;span class="nv"&gt;CUBEJS_AWS_S3_OUTPUT_LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;S3 OUTPUT LOCATION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's create a sample data schema for our events. Cube.js uses the data schema to generate SQL code, which will be executed in the database. The data schema is not a replacement for SQL. It is designed to make SQL reusable and give it a structure while preserving all of its power. We can build complex data models with Cube.js data schema. You can learn more about &lt;a href="https://cube.dev/docs/getting-started-cubejs-schema" rel="noopener noreferrer"&gt;Cube.js data schema here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;schema/Events.js&lt;/code&gt; file with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Events`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    SELECT
      event_id,
      event,
      platform,
      derived_tstamp,
      domain_sessionidx,
      domain_sessionid,
      domain_userid,
      ROW_NUMBER() OVER (PARTITION BY domain_sessionid ORDER BY derived_tstamp) AS event_in_session_index
    FROM
       analytics.snowplow_events
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`derived_tstamp`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`event_id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please, note that we query &lt;code&gt;snowplow_events&lt;/code&gt; table from &lt;code&gt;analytics&lt;/code&gt; database.&lt;br&gt;
Your database and table name may be different&lt;/p&gt;

&lt;p&gt;Now, we can start Cube.js server and open &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;. In development mode, Cube.js run Playground. It is an application to help you explore the data schema and send test queries.&lt;/p&gt;

&lt;p&gt;Let's test our newly created data schema!&lt;/p&gt;

&lt;p&gt;Cube.js accepts queries as JSON objects in the &lt;a href="https://cube.dev/docs/query-format" rel="noopener noreferrer"&gt;specific query format&lt;/a&gt;. Playground lets you visually build and explore queries. For example, we can construct the test query to load all the events over time. Also, you can always inspect the underlying JSON query by clicking &lt;strong&gt;JSON Query&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcube.dev%2Fdownloads%2Fmedia%2Fweb-analytics-json-query.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcube.dev%2Fdownloads%2Fmedia%2Fweb-analytics-json-query.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can explore other queries as well, test different charting libraries used to&lt;br&gt;
visualize results and explore the frontend javascript code. If you are just starting with Cube.js I recommend checking &lt;a href="https://cube.dev/blog/cubejs-open-source-dashboard-framework-ultimate-guide/" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;In the next part, we'll start working on the frontend application and will&lt;br&gt;
steadily build out our data schema.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Introducing a Drill Down Table API in Cube.js</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 25 Jun 2020 17:05:25 +0000</pubDate>
      <link>https://dev.to/keydunov/introducing-a-drill-down-table-api-in-cube-js-89l</link>
      <guid>https://dev.to/keydunov/introducing-a-drill-down-table-api-in-cube-js-89l</guid>
      <description>&lt;p&gt;Since the release of drill down support in version 0.19.23, you can build interfaces to let users dive deeper into visualizations and data tables. The common use case for this feature is to let users click on a spike on the chart to find out what caused it, or to inspect a particular step of the funnel—who has converted and who has not.&lt;/p&gt;

&lt;p&gt;In this blog post, I'll show you how to define drill downs in the data schema and build an interface to let users explore the underlying chart's data. If you're just starting with Cube.js, I highly recommend beginning with this &lt;a href="https://cube.dev/blog/cubejs-open-source-dashboard-framework-ultimate-guide/"&gt;Cube.js 101 tutorial&lt;/a&gt; and then coming back here. Also, if you have any questions, don't hesitate to ask them in our &lt;a href="http://slack.cube.dev/"&gt;Slack community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check &lt;a href="https://drill-downs.cubecloudapp.dev/#/"&gt;the online demo of the example here&lt;/a&gt;, and &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/drill-downs"&gt;the source code is available on GitHub.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VsMgaeEm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/KzGwEwQLSEuDtfHiJSZe" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VsMgaeEm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/KzGwEwQLSEuDtfHiJSZe" alt="drill-down-demo.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start hacking! 💻&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining a Drill Down in the Data Schema
&lt;/h2&gt;

&lt;p&gt;Let's start by setting up a new project with Cube.js and configuring drill down support in the data schema. We'll use &lt;a href="https://www.postgresql.org/"&gt;PostgresQL&lt;/a&gt; and our example e-commerce dataset for this tutorial. You can download and import it by running the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://cube.dev/downloads/ecom-dump.sql &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ecom-dump.sql
&lt;span class="nv"&gt;$ &lt;/span&gt;createdb ecom
&lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;--dbname&lt;/span&gt; ecom &lt;span class="nt"&gt;-f&lt;/span&gt; ecom-dump.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, install the Cube.js CLI if you don't have it already, and create a new project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;cubejs-cli
&lt;span class="nv"&gt;$ &lt;/span&gt;cubejs create drill-downs &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Make sure you have the following credentials in the .env file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CUBEJS_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SECRET
&lt;span class="nv"&gt;CUBEJS_DB_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
&lt;span class="nv"&gt;CUBEJS_DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ecom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, we're ready to launch the Cube.js server and navigate to the playground running at &lt;a href="http://localhost:4000/"&gt;http://localhost:4000&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once you're in the playground, navigate to the Schema tab. Then select the &lt;strong&gt;orders&lt;/strong&gt; and &lt;strong&gt;users&lt;/strong&gt; tables and click Generate Schema, as in the screenshot below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N6z5TucB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/3zOXy0SRyWpBUPHNJEQI" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N6z5TucB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/3zOXy0SRyWpBUPHNJEQI" alt="Screen_Shot_2020-06-19_at_7.42.27_PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will generate a basic data schema for users and orders tables, which already includes the &lt;code&gt;drillMembers&lt;/code&gt; property on the &lt;code&gt;count&lt;/code&gt; measure. The &lt;code&gt;drillMembers&lt;/code&gt; property contains a list of dimensions that will be used to show the underlying data when drilling into that measure.&lt;/p&gt;

&lt;p&gt;Let's take a closer look at the &lt;code&gt;Orders&lt;/code&gt; cube and its &lt;code&gt;count&lt;/code&gt; measure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;drillMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It already has the basic dimensions listed in the &lt;code&gt;drillMembers&lt;/code&gt; property: &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;createdAt&lt;/code&gt;. We can add additional dimensions to that list. We also can reference dimensions from joined cubes—in our case, from Users.&lt;/p&gt;

&lt;p&gt;Let's add more dimensions to the &lt;code&gt;drillMembers&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;drillMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's all we need in the data schema to build our drill down. On the frontend, we're going to make a bar chart to display orders over time. When a user clicks on the bar, our app will display the table inside the modal window, with details about the orders in that bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Drill Down UI
&lt;/h2&gt;

&lt;p&gt;We'll use Cube.js templates to generate a frontend app. Navigate to the Dashboard App tab and select the Material-UI React Dashboard. It will take several minutes to set up the Dashboard App and install all the dependencies inside the &lt;code&gt;dashboard-app&lt;/code&gt; folder in your project.&lt;/p&gt;

&lt;p&gt;Please note: although we use React in this example, you can build the same drill down in &lt;a href="https://cube.dev/docs/@cubejs-client-vue"&gt;Vue.js&lt;/a&gt;, &lt;a href="https://cube.dev/docs/@cubejs-client-ngx"&gt;Angular&lt;/a&gt;, or &lt;a href="https://cube.dev/docs/@cubejs-client-core"&gt;Vanilla JS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first step is to render a bar chart. We're going to plot the count of orders over time, grouped by the status. Eventually, we want to let users click on a specific group and day to explore the underlying orders—e.g., orders created on June 20 and already shipped.&lt;/p&gt;

&lt;p&gt;Let's create a &lt;code&gt;dashboard-app/src/DrillDownExample.js&lt;/code&gt; file with the following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCubeQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@cubejs-client/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BarChart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ResponsiveContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;XAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;YAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;CartesianGrid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Tooltip&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RechartsTooltip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Legend&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recharts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#FF6492&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#141446&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#7A77FF&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;timeDimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;dimension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.createdAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dateRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;last 30 days&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DrillDownExample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCubeQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ResponsiveContainer&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BarChart&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chartPivot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CartesianGrid&lt;/span&gt; &lt;span class="na"&gt;strokeDasharray&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"3 3"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;XAxis&lt;/span&gt; &lt;span class="na"&gt;dataKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"x"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;YAxis&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RechartsTooltip&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Legend&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seriesNames&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Bar&lt;/span&gt;
                &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;dataKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;stackId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;
                &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;BarChart&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ResponsiveContainer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;DrillDownExample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The code snippet above is pretty straightforward. First, we load data with the &lt;code&gt;useCubeQuery&lt;/code&gt; hook and render it later with Recharts. Next, let's add some interactivity and let users click on the bars!&lt;/p&gt;

&lt;p&gt;To be able to show the underlying data, we first need to figure out where the user clicked on the chart, and then construct a query to Cube.js to load that data. The user can click on any day in our bar chart and on any status of the order within that day. To describe that location, Cube.js uses two variables: &lt;code&gt;xValues&lt;/code&gt; and &lt;code&gt;yValues&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, the following values mean that the user wants to explore processing orders on June 6:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;xValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2020-06-06T00:00:00.000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;yValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To generate a query that returns data for a drill down table, we need to use the &lt;a href="https://cube.dev/docs/@cubejs-client-core#result-set-drill-down"&gt;ResultSet#drillDown()&lt;/a&gt; method. If we run it with the above values, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drillDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;xValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2020-06-06T00:00:00.000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;yValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;it will return the query, which has all the dimensions from the &lt;code&gt;drillMembers&lt;/code&gt; property in the data schema, as well as all required filters to specifically load processing orders on June 6.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"measures"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dimensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Orders.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Orders.status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Users.firstName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Users.city"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dimension"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Orders.count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"measureFilter"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"member"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Orders.status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"operator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"equals"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"processing"&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeDimensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dimension"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Orders.createdAt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dateRange"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"2020-06-06T00:00:00.000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"2020-06-06T23:59:59.999"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once we have the drill down query, we can use it to load data from the Cube.js API.&lt;/p&gt;

&lt;p&gt;To get the values for &lt;code&gt;xValues&lt;/code&gt; and &lt;code&gt;yValues&lt;/code&gt; properties, we will use the &lt;a href="https://cube.dev/docs/@cubejs-client-core#result-set-chart-pivot"&gt;ResultSet#chartPivot()&lt;/a&gt; and &lt;a href="https://cube.dev/docs/@cubejs-client-core/#result-set-series-names"&gt;ResultSet#seriesNames()&lt;/a&gt; methods. &lt;a href="https://cube.dev/docs/@cubejs-client-core#result-set-chart-pivot"&gt;chartPivot()&lt;/a&gt; returns &lt;code&gt;xValues&lt;/code&gt; for every data row, and &lt;a href="https://cube.dev/docs/@cubejs-client-core#result-set-series-names"&gt;seriesNames(&lt;/a&gt;) returns &lt;code&gt;yValues&lt;/code&gt; per series. We're going to use these methods to pass &lt;code&gt;xValues&lt;/code&gt; and &lt;code&gt;yValues&lt;/code&gt; to the Recharts to make sure we have them in the &lt;code&gt;onClick&lt;/code&gt; handler.&lt;/p&gt;

&lt;p&gt;First, let's create a click handler, which will accept &lt;code&gt;xValues&lt;/code&gt; and &lt;code&gt;yValues&lt;/code&gt;, generate a drill down query, and store it in the state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;drillDownQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDrillDownQuery&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleBarClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yValues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xValues&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setDrillDownQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drillDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;xValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xValues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;yValues&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we need to make sure we pass both &lt;code&gt;xValues&lt;/code&gt; and &lt;code&gt;yValues&lt;/code&gt; to the &lt;code&gt;handleBarClick&lt;/code&gt;. Since we pass &lt;code&gt;resultSet.chartPivot()&lt;/code&gt; to the Recharts &lt;code&gt;&amp;lt;BarChart /&amp;gt;&lt;/code&gt; component as a &lt;code&gt;data&lt;/code&gt; property, the &lt;code&gt;xValues&lt;/code&gt; will be available as the property on the &lt;code&gt;event&lt;/code&gt; object in the &lt;code&gt;onClick&lt;/code&gt; callback. To pass &lt;code&gt;yValues&lt;/code&gt;, we need to make the following changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-{resultSet.seriesNames().map(({ key }, index) =&amp;gt; {
&lt;/span&gt;&lt;span class="gi"&gt;+{resultSet.seriesNames().map(({ key, yValues }, index) =&amp;gt; {
&lt;/span&gt;  return (
    &amp;lt;Bar
      key={key}
      dataKey={key}
      stackId="a"
      fill={colors[index]}
&lt;span class="gi"&gt;+     onClick={event =&amp;gt; handleBarClick(event, yValues)}
&lt;/span&gt;    /&amp;gt;
  );
&lt;span class="err"&gt;})}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, as we have &lt;code&gt;drillDownQuery&lt;/code&gt; in the state, we can query it in our component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drillDownResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useCubeQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;drillDownQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;drillDownQuery&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Later, you can use &lt;code&gt;drillDownResponse&lt;/code&gt; to render the drill down data however you want. In our example, we use Material-UI Kit and render it as a table within the modal window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VGhNDCAT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/EDmUly6kQNGqtm5cKGMF" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VGhNDCAT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://media.graphcms.com/EDmUly6kQNGqtm5cKGMF" alt="Screen_Shot_2020-06-22_at_8.12.55_PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you found this tutorial helpful for adding interactive drill downs to your application! You can check the online demo of the example here, and the source code is available on GitHub.&lt;/p&gt;

&lt;p&gt;If you have any questions, please don't hesitate to reach out to me in &lt;a href="http://slack.cube.dev/"&gt;Cube.js Slack community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>D3 Dashboard Tutorial  with Cube.js</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 16 Jan 2020 17:13:36 +0000</pubDate>
      <link>https://dev.to/keydunov/d3-dashboard-tutorial-with-cube-js-ehb</link>
      <guid>https://dev.to/keydunov/d3-dashboard-tutorial-with-cube-js-ehb</guid>
      <description>&lt;p&gt;In this tutorial, I’ll cover building a basic dashboard application with &lt;a href="//cube.dev"&gt;Cube.js&lt;/a&gt; and the most popular library for visualizing data—&lt;a href="//d3js.org"&gt;D3.js&lt;/a&gt;. Although Cube.js doesn’t provide a visualization layer itself, it is very easy to integrate with any existing charting library. Additionally, you can use &lt;a href="https://cube.dev/templates/" rel="noopener noreferrer"&gt;Cube.js Templates&lt;/a&gt; to scaffold a frontend application with your favorite charting library, frontend framework, and UI kit. The scaffolding engine will wire it all together and configure it to work with the Cube.js backend.&lt;/p&gt;

&lt;p&gt;You can check &lt;a href="http://d3-dashboard-demo.cube.dev/" rel="noopener noreferrer"&gt;the online demo of this dashboard here&lt;/a&gt; and &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/d3-dashboard" rel="noopener noreferrer"&gt;the complete source code of the example app is available on Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We are going to use Postgres to store our data. Cube.js will connect to it and act as a middleware between the database and the client, providing API, abstraction, caching, and a lot more. On the frontend, we’ll have React with Material UI and D3 for chart rendering. Below, you can find a schema of the whole architecture of the example app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2Fschema-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2Fschema-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have any questions while going through this guide, please feel free to join this &lt;a href="http://slack.cube.dev/" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt; and post your question there.&lt;/p&gt;

&lt;p&gt;Happy Hacking! 💻&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a Database and Cube.js
&lt;/h2&gt;

&lt;p&gt;The first thing we need to have in place is a database. We’ll use Postgres for this tutorial. However, you can use your favorite SQL (or Mongo) database. Please refer to the &lt;a href="https://cube.dev/docs/connecting-to-the-database" rel="noopener noreferrer"&gt;Cube.js documentation on how to connect to different databases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you don’t have any data for the dashboard, you can load our sample e-commerce Postgres dataset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://cube.dev/downloads/ecom-dump-d3-example.sql &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ecom-dump.sql
&lt;span class="nv"&gt;$ &lt;/span&gt;createdb ecom
&lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;--dbname&lt;/span&gt; ecom &lt;span class="nt"&gt;-f&lt;/span&gt; ecom-dump.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, as we have data in the database, we’re ready to create the Cube.js Backend service. Run the following commands in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; cubejs-cli
&lt;span class="nv"&gt;$ &lt;/span&gt;cubejs create d3-dashboard &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commands above install Cube.js CLI and create a new service, configured to work with a Postgres database.&lt;/p&gt;

&lt;p&gt;Cube.js uses environment variables for configuration. It uses environment variables starting with &lt;code&gt;CUBEJS_&lt;/code&gt;. To configure the connection to our database, we need to specify the DB type and name. In the Cube.js project folder, replace the contents of .env with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CUBEJS_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SECRET
&lt;span class="nv"&gt;CUBEJS_DB_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
&lt;span class="nv"&gt;CUBEJS_DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ecom
&lt;span class="nv"&gt;CUBEJS_WEB_SOCKETS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s start the server and open the developer playground at &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to create a &lt;a href="https://cube.dev/docs/getting-started-cubejs-schema" rel="noopener noreferrer"&gt;Cube.js data schema&lt;/a&gt;. Cube.js uses the data schema to generate an SQL code, which will be executed in your database. Cube.js Playground can generate simple schemas based on the database’s tables. Let’s navigate to the Schema page and generate the schemas we need for our dashboard. Select the &lt;code&gt;line_items&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, &lt;code&gt;product_categories&lt;/code&gt;, and &lt;code&gt;users&lt;/code&gt; tables and click &lt;strong&gt;Generate Schema&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F2-screenshot-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F2-screenshot-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s test our newly generated schema. Go to the Build page and select a measure in the dropdown. You should be able to see a simple line chart. You can choose D3 from the charting library dropdown to see an example of D3 visualization. Note that it is just an example and you can always customize and expand it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F2-screenshot-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F2-screenshot-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s make some updates to our schema. The schema generation makes it easy to get started and test the dataset, but for real-world use cases, we almost always need to make manual changes. &lt;/p&gt;

&lt;p&gt;In the schema, we define measures and dimensions and how they map into SQL queries. You can find extensive documentation about &lt;a href="https://cube.dev/docs/getting-started-cubejs-schema" rel="noopener noreferrer"&gt;data schema here&lt;/a&gt;. We’re going to add a &lt;code&gt;priceRange&lt;/code&gt; dimension to the Orders cube. It will indicate whether the total price of the order falls into one of the buckets: “$0 - $100”, “$100 - $200”, “$200+”.&lt;/p&gt;

&lt;p&gt;To do this, we first need to define a &lt;code&gt;price&lt;/code&gt; dimension for the order. In our database, &lt;code&gt;orders&lt;/code&gt; don’t have a price column, but we can calculate it based on the total price of the &lt;code&gt;line_items&lt;/code&gt; inside the order. Our schema has already automatically indicated and defined a relationship between the &lt;code&gt;Orders&lt;/code&gt; and &lt;code&gt;LineTimes&lt;/code&gt; cubes. You can read more about &lt;a href="https://cube.dev/docs/joins" rel="noopener noreferrer"&gt;joins here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// You can check the belongsTo join&lt;/span&gt;
&lt;span class="c1"&gt;// to the Orders cube inside the LineItems cube&lt;/span&gt;
&lt;span class="nx"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.order_id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`belongsTo`&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;LineItems&lt;/code&gt; cube has &lt;code&gt;price&lt;/code&gt; measure with a &lt;code&gt;sum&lt;/code&gt; type. We can reference this measure from the &lt;code&gt;Orders&lt;/code&gt; cube as a dimension and it will give us the sum of all the line items that belong to that order. It’s called a &lt;code&gt;subQuery&lt;/code&gt; dimension; you can &lt;a href="https://cube.dev/docs/subquery" rel="noopener noreferrer"&gt;learn more about it here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add the following dimension to the Orders cube&lt;/span&gt;
&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;LineItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`currency`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, based on this dimension, we can create a &lt;code&gt;priceRange&lt;/code&gt; dimension. We’ll use a &lt;a href="https://cube.dev/docs/dimensions#parameters-case" rel="noopener noreferrer"&gt;case statement&lt;/a&gt; to define a conditional logic for our price buckets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add the following dimension to the Orders cube&lt;/span&gt;
&lt;span class="nx"&gt;priceRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt; 101`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`$0 - $100`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt; 201`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`$100 - $200`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`$200+`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s try our newly created dimension! Go to the Build page in the playground, select the Orders count measure with the Orders price range dimension. You can always check the generated SQL by clicking the &lt;strong&gt;SQL&lt;/strong&gt; button on the control bar.&lt;/p&gt;

&lt;p&gt;That’s it for the backend! In the next part, we’ll look closer at how to render the results of our queries with D3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering Chart with D3.js
&lt;/h2&gt;

&lt;p&gt;Now, as we can build our first chart, let’s inspect the example code playground uses to render it with the D3. Before that, we need to understand how Cube.js accepts and processes a query and returns the result back.&lt;/p&gt;

&lt;p&gt;A Cube.js query is a simple JSON object containing several properties. The main properties of the query are &lt;code&gt;measures&lt;/code&gt;, &lt;code&gt;dimensions&lt;/code&gt;, &lt;code&gt;timeDimensions&lt;/code&gt;, and &lt;code&gt;filters&lt;/code&gt;. You can learn more about the &lt;a href="https://cube.dev/docs/query-format" rel="noopener noreferrer"&gt;Cube.js JSON query format and its properties here&lt;/a&gt;. You can always inspect the JSON query in the playground by clicking the &lt;strong&gt;JSON Query&lt;/strong&gt; button next to the chart selector.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F3-screenshot-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F3-screenshot-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube.js backend accepts this query and then uses it and the schema we created earlier to generate an SQL query. This SQL query will be executed in our database and the result will be sent back to the client.&lt;/p&gt;

&lt;p&gt;Although Cube.js can be queried via plain HTTP REST API, we’re going to use the Cube.js JavaScript client library. Among other things it provides useful tools to process the data after it has been returned from the backend.&lt;/p&gt;

&lt;p&gt;Once the data is loaded, the Cube.js client creates a &lt;code&gt;ResultSet&lt;/code&gt; object, which provides a set of methods to access and manipulate the data. We’re going to use two of them now: &lt;code&gt;ResultSet.series&lt;/code&gt; and &lt;code&gt;ResultSet.chartPivot&lt;/code&gt;. You can learn about all the features of the &lt;a href="https://cube.dev/docs/@cubejs-client-core" rel="noopener noreferrer"&gt;Cube.js client library in the docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ResultSet.series&lt;/code&gt; method returns an array of data series with key, title, and series data. The method accepts one argument—&lt;code&gt;pivotConfig&lt;/code&gt;. It is an object, containing rules about how the data should be pivoted; we’ll talk about it a bit. In a line chart, each series is usually represented by a separate line. This method is useful for preparing data in the format expected by D3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For query&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stories.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;timeDimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;dimension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stories.time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dateRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2015-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2015-12-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ResultSet.series() will return&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stories.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stories Count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;series&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2015-01-01T00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27120&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2015-02-01T00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25861&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2015-03-01T00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;29661&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next method we need is &lt;code&gt;ResultSet.chartPivot&lt;/code&gt;. It accepts the same &lt;code&gt;pivotConfig&lt;/code&gt; argument and returns an array of data with values for the X-axis and for every series we have.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For query&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stories.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;timeDimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;dimension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stories.time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dateRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2015-01-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2015-12-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ResultSet.chartPivot() will return&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2015-01-01T00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stories.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27120&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2015-02-01T00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stories.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25861&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2015-03-01T00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stories.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;29661&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned above, the &lt;code&gt;pivotConfig&lt;/code&gt; argument is an object for controlling how to transform, or pivot, data. The object has two properties: &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, both are arrays. By adding measures or dimensions to one of them, you can control what goes to the X-axis and what goes to the Y-axis. For a query with one &lt;code&gt;measure&lt;/code&gt; and one &lt;code&gt;timeDimension&lt;/code&gt;, &lt;code&gt;pivotConfig&lt;/code&gt; has the following default value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`CubeName.myTimeDimension.granularity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`measures`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, ‘measures’ is a special value, meaning that all the measures should go to the Y-axis. In most cases, the default value of the &lt;code&gt;pivotConfig&lt;/code&gt; should work fine. In the next part, I’ll show you when and how we need to change it.&lt;/p&gt;

&lt;p&gt;Now, let’s look at the frontend code playground generates when we select a D3 chart. Select a measure in the playground and change the visualization type to the D3. Next, click the &lt;strong&gt;Code&lt;/strong&gt; to inspect the frontend code to render the chart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F3-screenshot-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F3-screenshot-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the full source code from that page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cubejs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cubejs-client/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryRenderer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cubejs-client/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Spin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;antd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COLORS_SERIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#FF6492&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#141446&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#7A77FF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chartType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Set the dimensions and margins of the graph&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientWidth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;svg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;height&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;translate(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Prepare data in D3 format&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;series&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;series&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="c1"&gt;// color palette&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleOrdinal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;COLORS_SERIES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Add X axis&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chartPivot&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;translate(0,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;axisBottom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Add Y axis&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleLinear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)))])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;axisLeft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Draw the lines&lt;/span&gt;
  &lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.line&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stroke&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stroke-width&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lineRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// change to your actual endpoint&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cubejsApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cubejs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1NzkwMjU0ODcsImV4cCI6MTU3OTExMTg4N30.nUyJ4AEsNk9ks9C8OwGPCHrcTXyJtqJxm02df7RGnQU&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/cubejs-api/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderChart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="na"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Spin&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ChartRenderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QueryRenderer&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;measures&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeDimensions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dimension&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.createdAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;granularity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;filters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;cubejsApi&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cubejsApi&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;renderChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;ChartRenderer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The React component that renders the chart is just a single line wrapping a &lt;code&gt;draw&lt;/code&gt; function, which does the entire job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lineRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;line&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a lot going on in this &lt;code&gt;draw&lt;/code&gt; function. Although it renders a chart already, think about it as an example and a good starting point for customization. As we’ll work on our own dashboard in the next part, I’ll show you how to do it.&lt;/p&gt;

&lt;p&gt;Feel free to click the &lt;strong&gt;Edit&lt;/strong&gt; button and play around with the code in Code Sandbox.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F3-screenshot-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F3-screenshot-3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Frontend Dashboard
&lt;/h2&gt;

&lt;p&gt;Now we are ready to build our frontend application. We’re going to use Cube.js Templates, which is a scaffolding engine for quickly creating frontend applications configured to work with the Cube.js backend. It provides a selection of different frontend frameworks, UI kits, and charting libraries to mix together. We’ll pick React, Material UI, and D3.js. Let’s navigate to the Dashboard App tab and create a new dashboard application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F4-screenshot-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F4-screenshot-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It could take several minutes to generate an app and install all the dependencies. Once it is done, you will have a &lt;code&gt;dashboard-app&lt;/code&gt; folder inside your Cube.js project folder. To start a frontend application, either go to the “Dashboard App” tab in the playground and hit the “Start” button, or run the following command inside the dashboard-app folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the Cube.js backend process is up and running since our frontend application uses its API. The frontend application is running on &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;. If you open it in your browser, you should be able to see an empty dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F4-screenshot-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F4-screenshot-2.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add a chart to the dashboard, we can either build it in the playground and click the “add to dashboard” button or edit the &lt;code&gt;src/pages/DashboardPage.js&lt;/code&gt; file in the &lt;code&gt;dashboard-app&lt;/code&gt; folder. Let’s go with the latter option. Among other things, this file declares the &lt;code&gt;DashboardItems&lt;/code&gt; variable, which is an array of queries for charts.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;dashboard-app/src/pages/DashboardPage.js&lt;/code&gt; to add charts to the dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-const DashboardItems = [];
&lt;/span&gt;&lt;span class="gi"&gt;+const DashboardItems = [
+  {
+    id: 0,
+    name: "Orders last 14 days",
+    vizState: {
+      query: {
+        measures: ["Orders.count"],
+        timeDimensions: [
+          {
+            dimension: "Orders.createdAt",
+            granularity: "day",
+            dateRange: "last 14 days"
+          }
+        ],
+        filters: []
+      },
+      chartType: "line"
+    }
+  },
+  {
+    id: 1,
+    name: "Orders Status by Customers City",
+    vizState: {
+      query: {
+        measures: ["Orders.count"],
+        dimensions: ["Users.city", "Orders.status"],
+        timeDimensions: [
+          {
+            dimension: "Orders.createdAt",
+            dateRange: "last year"
+          }
+        ]
+      },
+      chartType: "bar",
+      pivotConfig: {
+        x: ["Users.city"],
+        y: ["Orders.status", "measures"]
+      }
+    }
+  },
+  {
+    id: 3,
+    name: "Orders by Product Categories Over Time",
+    vizState: {
+      query: {
+        measures: ["Orders.count"],
+        timeDimensions: [
+          {
+            dimension: "Orders.createdAt",
+            granularity: "month",
+            dateRange: "last year"
+          }
+        ],
+        dimensions: ["ProductCategories.name"]
+      },
+      chartType: "area"
+    }
+  },
+  {
+    id: 3,
+    name: "Orders by Price Range",
+    vizState: {
+      query: {
+        measures: ["Orders.count"],
+        filters: [
+          {
+            "dimension": "Orders.price",
+            "operator": "set"
+          }
+        ],
+        dimensions: ["Orders.priceRange"]
+      },
+      chartType: "pie"
+    }
+  }
+];
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see above, we’ve just added an array of Cube.js query objects.&lt;/p&gt;

&lt;p&gt;If you refresh the dashboard, you should be able to see your charts!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F4-screenshot-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fcube-js%2Fcube.js%2Fraw%2Fmaster%2Fguides%2Fd3-dashboard%2Fstatic%2Fimages%2F4-screenshot-3.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can notice that one of our queries has the &lt;code&gt;pivotConfig&lt;/code&gt; defined as the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;pivotConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Users.city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;measures&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I mentioned in the previous part the default value for the &lt;code&gt;pivotConfig&lt;/code&gt; usually works fine, but in some cases like this one, we need to adjust it to get the desired result. We want to plot a bar chart here with the cities on the X-Axis and the number of orders on the Y-Axis grouped by the orders' statuses. That is exactly what we are passing here in the &lt;code&gt;pivotConfig&lt;/code&gt;: &lt;code&gt;Users.city&lt;/code&gt; to the X-Axis and measures with &lt;code&gt;Orders.status&lt;/code&gt; to the Y-axis to get the grouped result.&lt;/p&gt;

&lt;p&gt;To customize the rendering of the charts, you can edit the &lt;code&gt;dashboard-app/src/pages/ChartRenderer.js&lt;/code&gt; file. It should look familiar to what we saw in the previous part.&lt;/p&gt;

&lt;p&gt;You can check &lt;a href="http://d3-dashboard-demo.cube.dev/" rel="noopener noreferrer"&gt;the online demo of this dashboard here&lt;/a&gt; and &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/d3-dashboard" rel="noopener noreferrer"&gt;the complete source code of the example app is available on Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Congratulations on completing this guide! 🎉&lt;/p&gt;

&lt;p&gt;I’d love to hear from you about your experience following this guide. Please send any comments or feedback you might have here in the comments or in this &lt;a href="http://slack.cube.dev/" rel="noopener noreferrer"&gt;Slack Community&lt;/a&gt;. Thank you and I hope you found this guide helpful!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Launching Cube.js Templates 📊 - the easiest way to build analytics dashboards and applications</title>
      <dc:creator>Artyom Keydunov</dc:creator>
      <pubDate>Thu, 21 Nov 2019 15:18:25 +0000</pubDate>
      <link>https://dev.to/keydunov/launching-cube-js-templates-the-easiest-way-to-build-analytics-dashboards-and-applications-4lj9</link>
      <guid>https://dev.to/keydunov/launching-cube-js-templates-the-easiest-way-to-build-analytics-dashboards-and-applications-4lj9</guid>
      <description>&lt;p&gt;Setting up a new project, writing tons of configurations, and wiring all the things together is hard and boring. It's fun to write actual application code, not Webpack config. That's why Create React App is so extremely popular. &lt;/p&gt;

&lt;p&gt;Same for analytics apps and dashboards. Although &lt;a href="https://github.com/cube-js/cube.js" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; takes care of all the backend, there are still a lot of things to set up and configure on the frontend - charting libraries, framework bindings, WebSockets for real-time dashboards and so on and so forth. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://cube.dev/templates/" rel="noopener noreferrer"&gt;Cube.js Templates&lt;/a&gt; to the rescue! Templates are open-source, ready-to-use frontend analytics apps. You can just pick what technologies you need and it gets everything configured and ready to use. &lt;/p&gt;

&lt;p&gt;React with WebSockets, Chart.js and Material UI? You got it. Template wires it all together and configure to work with the Cube.js backend. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/YsbF95tbSAQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Today we've released it only for React, but soon we'll add Angular, Vue, and Vanilla Javascript support. And it is open-sourced, same as Cube.js. Contributions are very welcomed! ❤️&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/cube-js" rel="noopener noreferrer"&gt;
        cube-js
      &lt;/a&gt; / &lt;a href="https://github.com/cube-js/cube" rel="noopener noreferrer"&gt;
        cube
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      📊  Cube — The Semantic Layer for Building Data Applications
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  5 Minute Tutorial
&lt;/h2&gt;

&lt;p&gt;If you want to try it out today, here is the 5-minute getting started tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Cube.js CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm isntall cubejs-cli &lt;span class="nt"&gt;-g&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create New Project and Connect your database
&lt;/h3&gt;

&lt;p&gt;Cube.js CLI has &lt;code&gt;create&lt;/code&gt; command to setup new project. We also need to pass a database type with &lt;code&gt;-d&lt;/code&gt; option. Here is the &lt;a href="https://cube.dev/docs/connecting-to-the-database" rel="noopener noreferrer"&gt;list of supported databases&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cubejs create hello-world &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once created, &lt;code&gt;cd&lt;/code&gt; into your new project and edit &lt;code&gt;.env&lt;/code&gt; file to configure the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CUBEJS_DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my_database
&lt;span class="nv"&gt;CUBEJS_DB_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
&lt;span class="nv"&gt;CUBEJS_API_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SUPER_SECRET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run the following command to start a dev server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And navigate to the Cube.js Playground at &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate Schema
&lt;/h3&gt;

&lt;p&gt;Cube.js uses schema to know how to query your database. The schema is written in javascript and could be quite complex with a lot of logic. But as we just getting started we can auto-generate a simple schema in the playground. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freact-dashboard.cube.dev%2Fimages%2F1-screenshot-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Freact-dashboard.cube.dev%2Fimages%2F1-screenshot-1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Cube.js Templates to create a frontend app
&lt;/h3&gt;

&lt;p&gt;As we already have a Cube.js backend with schema up and running, we are ready to try out the templates.&lt;/p&gt;

&lt;p&gt;Navigate to the "Dashboard App" tab in the playground. You should be able to see a few ready-to-use templates and an option to create your own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1suc88w9p7b6w16yr6xk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1suc88w9p7b6w16yr6xk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to click select whatever template works for you. Or you can mix different options and create your own template.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhxgrw6qdcmp68vjzbyfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhxgrw6qdcmp68vjzbyfg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you created your app from the template, you can start it from the Cube.js playground. You can edit it later in the &lt;code&gt;dashboard-app&lt;/code&gt; folder inside the project. &lt;/p&gt;

&lt;p&gt;That's it! Now, you have a full working both backend and frontend for your dashboard. You can follow &lt;a href="https://react-dashboard.cube.dev/" rel="noopener noreferrer"&gt;React Dashboard Guide&lt;/a&gt; or &lt;a href="https://real-time-dashboard.cube.dev/" rel="noopener noreferrer"&gt;Real-Time Dashboard Guide&lt;/a&gt; to learn how to customize the dashboard app and deploy it to production 🚀&lt;/p&gt;

&lt;p&gt;Please feel free to share your feedback or ask any questions in the comments below or in this &lt;a href="https://slack.cube.dev/" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>showdev</category>
      <category>githunt</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
