• Edit
  • Download
  • <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8">
      <title>ZingSoft Demo</title>
      <script nonce="undefined" src="https://cdn.zingchart.com/zingchart.min.js"></script>
      <script nonce="undefined" src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.2/marked.min.js"></script>
      <style>
        /* main.css */
    
        html,
        body {
          margin: 0;
          padding: 0;
        }
    
        html,
        body,
        #zingChart {
          height: 100%;
          width: 100%;
        }
    
        body {
          background-color: rgb(118, 118, 118);
          color: white;
          display: grid;
          font-family: sans-serif;
          grid-template-areas:
            'blank1'
            'title'
            'chart'
            'blank2';
          grid-template-rows: 1fr auto auto 1fr;
          justify-content: center;
          row-gap: 1rem;
          padding: 0;
        }
    
        header {
          grid-area: title;
          text-align: center;
    
          h1 {
            filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, 0.6));
          }
        }
    
        main {
          align-items: center;
          column-gap: 2rem;
          display: grid;
          grid-area: chart;
          grid-template-columns: 2fr 1fr;
          height: 100%;
        }
    
        section {
          height: fit-content;
          width: 100%;
        }
    
        #sectionChart {
          align-items: center;
          background-color: #dbdbdb;
          border: 1rem solid #dbdbdb;
          border-radius: 10px;
          box-sizing: border-box;
          color: black;
          display: grid;
          filter: drop-shadow(2px 2px 8px rgba(0, 0, 0, 0.25));
          height: 45.5rem;
    
          #zingChart {
            border-radius: 5px;
            justify-self: flex-end;
            min-height: 38.9rem;
          }
    
          #chartQuery {
            display: grid;
            margin-top: 1rem;
            place-items: center;
            text-align: center;
    
            label[for='query'] {
              margin-bottom: 0.5rem;
            }
    
            #queryForm {
              display: flex;
              max-width: 60ch;
              width: 100%;
    
              #query {
                border: none;
                border-top-left-radius: 5px;
                border-bottom-left-radius: 5px;
                font-size: 1.1rem;
                padding: 0.5ch 1ch;
                width: 100%;
              }
    
              button {
                border: none;
                border-top-right-radius: 5px;
                border-bottom-right-radius: 5px;
                cursor: pointer;
                padding: 0 1.5ch;
              }
    
              button:hover {
                background-color: #e3e3e3;
              }
    
              button:active {
                background-color: #d0d0d0;
              }
            }
          }
        }
    
        #sectionOutput {
          background-color: #dbdbdb;
          border: 1rem solid #dbdbdb;
          border-radius: 10px;
          box-sizing: border-box;
          color: black;
          display: grid;
          grid-template-columns: 1fr;
          grid-template-rows: 1fr auto;
          filter: drop-shadow(2px 2px 8px rgba(0, 0, 0, 0.25));
          height: 45.5rem;
          justify-items: center;
          justify-self: flex-start;
          row-gap: 2ch;
          width: 52ch;
    
          output {
            background-color: white;
            border-radius: 5px;
            box-sizing: border-box;
            display: block;
            overflow-y: scroll;
            padding: 1ch;
            width: 100%;
    
            &> :first-child {
              margin-block-start: 0;
            }
          }
    
          output.error {
            color: red;
            display: grid;
            font-size: 1.5rem;
            place-items: center;
            text-align: center;
          }
    
          output:has(.loader.active) {
            display: grid;
            place-items: center;
          }
    
          button {
            background-color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, 0.35));
            padding: 1ch 3ch;
            width: fit-content;
          }
    
          button:hover {
            filter: drop-shadow(2px 2px 1px rgba(0, 0, 0, 0.2));
          }
    
          button:active {
            filter: none;
          }
    
          .wrapper {
            display: grid;
            justify-items: center;
            row-gap: 1rem;
            width: fit-content;
          }
        }
    
        @media (max-width: 1500px) {
          body {
            grid-template-columns: 1fr;
            padding: 0;
            width: 100%;
          }
    
          main {
            grid-template-columns: 1fr;
            grid-template-rows: auto auto;
            justify-items: center;
            row-gap: 2rem;
          }
    
          main #sectionChart {
            height: 41.8rem;
            width: 90%;
          }
    
          main #sectionChart #zingChart {
            height: auto;
            min-height: 35rem;
            width: 100%;
          }
    
          main #sectionOutput {
            margin-bottom: 2rem;
            justify-self: unset;
            width: 90%;
          }
        }
    
        @media (max-width: 1000px) {
          main #sectionChart {
            height: 31.6rem;
          }
    
          main #sectionChart #zingChart {
            min-height: 25rem;
          }
        }
    
        /*****************************************/
        /***         Loading Animation         ***/
        /*** https://css-loaders.com/colorful/ ***/
        /*****************************************/
    
        /* HTML: <div class="loader"></div> */
        .loader {
          display: none;
        }
    
        .loader.active {
          width: 40px;
          aspect-ratio: 1;
          border-radius: 50%;
          margin-top: -30px;
          display: flex;
          justify-content: center;
          overflow: hidden;
          transform-origin: 50% 116.5%;
          animation: l17 2s infinite linear;
        }
    
        .loader.active:before {
          content: '';
          min-width: 233%;
          height: 233%;
          background: radial-gradient(farthest-side, #6fde99 90%, #0000) top,
            radial-gradient(farthest-side, #61e9d8 90%, #0000) left,
            radial-gradient(farthest-side, #ff9c85 90%, #0000) bottom,
            radial-gradient(farthest-side, #ffe295 90%, #0000) right;
          background-size: 43% 43%;
          background-repeat: no-repeat;
          animation: inherit;
          animation-direction: reverse;
        }
    
        @keyframes l17 {
          100% {
            transform: rotate(360deg);
          }
        }
      </style>
    </head>
    
    <body>
      <header>
        <h1>Analyze Your ZingChart with AI</h1>
      </header>
      <main>
        <section id="sectionChart">
          <div id="zingChart"></div>
          <div id="chartQuery">
            <label for="query">Ask AI a Question About the Chart</label>
            <form id="queryForm">
              <input id="query" name="query" type="text" placeholder="Your query here..." autocomplete="off" />
              <button>ASK</button>
            </form>
          </div>
        </section>
        <section id="sectionOutput">
          <output>
            <div class="loader"></div>
          </output>
          <button>Analyze Chart</button>
        </section>
      </main>
      <script>
        ZC.LICENSE = ["569d52cefae586f634c54f86dc99e6a9", "b55b025e438fa8a98e32482b5f768ff5"]; // main.js
        /*******************************************/
        /* HELPER FUNCTIONS FOR THIS DEMO - IGNORE */
        /*******************************************/
        var __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
          function adopt(value) {
            return value instanceof P ? value : new P(function(resolve) {
              resolve(value);
            });
          }
          return new(P || (P = Promise))(function(resolve, reject) {
            function fulfilled(value) {
              try {
                step(generator.next(value));
              } catch (e) {
                reject(e);
              }
            }
    
            function rejected(value) {
              try {
                step(generator["throw"](value));
              } catch (e) {
                reject(e);
              }
            }
    
            function step(result) {
              result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
            }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
          });
        };
        /*****************/
        /* PROGRAM START */
        /*****************/
        const SERVER_URL = 'https://app-pmfi2cavkq-uc.a.run.app';
        const ANALYZE_CHART_API = '/analyze-chart';
        const ANALYZE_CHART_QUERY_API = '/analyze-chart-query';
        const CHART_ID = 'zingChart';
        let chartData = {
          warrants: [3, 3, 4, 6, 4, 3, 3, 5, 6, 7, 9, 10],
          orders: [3, 3, 4, 7, 4, 3, 3, 7, 8, 9, 11, 12],
          subpoenas: [3, 3, 4, 6, 4, 3, 3, 9, 10, 12, 13, 14],
          usRequests: [3, 3, 4, 6, 4, 3, 3, 10, 11, 13, 14, 15],
          governmentRequests: [3, 3, 4, 6, 4, 3, 3, 11, 12, 14, 15, 20],
        };
        let chartConfig = {
          type: 'area',
          globals: {
            fontColor: '#333',
            fontFamily: 'sans-serif',
          },
          title: {
            text: 'Government Requests by Volume',
            align: 'center',
            padding: '5px',
          },
          subtitle: {
            text: 'This area graph shows the total number of requests received over time, broken down by request type',
            align: 'center',
            fontColor: '#505050',
            padding: '10px',
          },
          legend: {
            align: 'center',
            border: 'none',
            item: {
              fontSize: '18px',
            },
            layout: '3x2',
            marker: {
              type: 'circle',
            },
            verticalAlign: 'bottom',
          },
          plot: {
            tooltip: {
              text: '%t:<br>%v Requests received between<br>July 2020 & June 2021',
              backgroundColor: '#F7F9FA',
              borderRadius: '4px',
              callout: true,
              fontColor: '#505050',
              fontSize: '18px',
              padding: '20px 35px',
            },
            aspect: 'spline',
          },
          scaleX: {
            labels: [
              'Jul 20',
              'Aug 20',
              'Sep 20',
              'Oct 20',
              'Nov 20',
              'Dec 20',
              'Jan 21',
              'Feb 21',
              'Mar 21',
              'Apr 21',
              'May 21',
              'Jun 21',
            ],
          },
          scaleY: {
            values: '0:16:2',
          },
          series: [{
              text: 'Arrest Warrants',
              values: chartData.warrants,
              alphaArea: 1,
              backgroundColor: '#62E9D8',
              hoverMarker: {
                backgroundColor: '#fff',
                borderColor: '#eee',
                size: '10px',
              },
              lineColor: '#ffffff',
              lineWidth: '3px',
              marker: {
                size: '0px',
              },
              zIndex: 10,
            },
            {
              text: ' Court Orders',
              values: chartData.orders,
              alphaArea: 1,
              backgroundColor: '#70DE99',
              hoverMarker: {
                backgroundColor: '#fff',
                borderColor: '#eee',
                size: '10px',
              },
              lineColor: '#ffffff',
              marker: {
                size: '0px',
              },
              zIndex: 9,
            },
            {
              text: 'Subpoenas',
              values: chartData.subpoenas,
              alphaArea: 1,
              backgroundColor: '#B2DC93',
              hoverMarker: {
                backgroundColor: '#fff',
                borderColor: '#eee',
                size: '10px',
              },
              lineColor: '#ffffff',
              marker: {
                size: '0px',
              },
              zIndex: 8,
            },
            {
              text: 'US Requests',
              values: chartData.usRequests,
              alphaArea: 1,
              backgroundColor: '#FFE295',
              hoverMarker: {
                backgroundColor: '#fff',
                borderColor: '#eee',
                size: '10px',
              },
              lineColor: '#ffffff',
              marker: {
                size: '0px',
              },
              zIndex: 7,
            },
            {
              text: 'Government Requests',
              values: chartData.governmentRequests,
              alphaArea: 1,
              backgroundColor: '#FF9C85',
              hoverMarker: {
                backgroundColor: '#fff',
                borderColor: '#eee',
                size: '10px',
              },
              lineColor: '#ffffff',
              marker: {
                size: '0px',
              },
              zIndex: 6,
            },
          ],
          gui: {
            contextMenu: {
              item: {
                fontColor: 'white',
              },
              customItems: [{
                text: 'Analyze Chart with AI',
                function: 'analyzeWithAI()',
                id: 'analyzeWithAI',
              }, ],
            },
          },
          mediaRules: [{
              maxWidth: 600,
              chart: {
                paddingTop: 30,
              },
            },
            {
              maxWidth: 600,
              subtitle: {
                text: 'This area graph shows the total number of requests\nreceived over time, broken down by request type',
                paddingBottom: 10,
              },
            },
            {
              maxWidth: 500,
              legend: {
                layout: '6x1',
              },
            },
            {
              maxWidth: 400,
              title: {
                text: 'Government Requests\nby Volume',
                paddingBottom: 20,
              },
            },
          ],
        };
        /**
         * Exports a given chart to a jpg image
         * @param {string} chartId The ID of the chart to export the image from
         * @return {string} The base64 encoded image data as a JPG
         */
        function exportChartImage(chartId) {
          return __awaiter(this, void 0, void 0, function*() {
            return new Promise((resolve, reject) => {
              try {
                zingchart.exec(chartId, 'getimagedata', {
                  filetype: 'jpg',
                  callback: (imagedata) => resolve(imagedata),
                });
              } catch (err) {
                reject(err);
              }
            });
          });
        }
        /**
         * @param {string} chartId The ID of the chart to export the image from
         * @param {string} [query] The query to send along with the chart
         * @return {string} The AI's response of the image
         */
        function analyzeChart(chartId, query) {
          return __awaiter(this, void 0, void 0, function*() {
            // Create the URL and Request Body for the AI request
            let url = SERVER_URL;
            let reqBody = {
              chartImage: yield exportChartImage(chartId)
            };
            // Determine what type of request it is
            if (query) {
              url += ANALYZE_CHART_QUERY_API;
              reqBody.query = query;
            } else {
              url += ANALYZE_CHART_API;
            }
            try {
              let response = yield fetch(url, {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json',
                },
                body: JSON.stringify(reqBody),
              });
              return yield response.json();
            } catch (err) {
              console.error('Trouble reaching endpoint', err);
            }
          });
        }
        /**
         * Analyze the current chart with AI and then output that analysis to the page.
         * Function exposed to window so it's available to ZingChart.
         * @param {string} [query] Query to send along with chart
         */
        window.analyzeWithAI = function(query) {
          let output = document.querySelector('output');
          output.innerHTML = /* HTML */ `<div class="loader"></div>`;
          output.classList.remove('error');
          let loader = document.querySelector('output > .loader');
          // Create simple loader counter for UX
          loader.classList.add('active');
          // Sends chart off to analyze
          analyzeChart(CHART_ID, query)
            .then((aiAnalysis) => {
              var _a, _b;
              loader.classList.remove('active');
              aiAnalysis = (_b = (_a = aiAnalysis.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content;
              if (!aiAnalysis) {
                output.innerHTML = 'Error: Trouble reaching AI server';
                output.classList.add('error');
              } else {
                let renderedMarkdown = marked.parse(aiAnalysis);
                output.innerHTML = renderedMarkdown;
              }
            })
            .catch((err) => {
              output.innerHTML = 'Error: Trouble reaching AI server';
              output.classList.add('error');
            });
        };
    
        function init() {
          // Render ZingChart
          zingchart.render({
            id: CHART_ID,
            data: chartConfig,
            height: '100%',
            // height: '400',
            width: '100%',
          });
          // Attach event listener to analyze chart on button click
          let analyzeChartBtn = document.querySelector('#sectionOutput button');
          analyzeChartBtn.addEventListener('click', () => analyzeWithAI());
          // Attach event listener to query form
          let queryForm = document.querySelector('form#queryForm');
          let queryInput = document.querySelector('input#query');
          queryForm.addEventListener('submit', (e) => {
            // Prevent the form from submitting
            e.preventDefault();
            // Grab the query out of the submitted form
            let query = new FormData(queryForm).get('query');
            // Analyze the query
            analyzeWithAI(query);
          });
          document.addEventListener('keydown', (e) => {
            if (e.key === '/') {
              // Don't add the / to the input field
              e.preventDefault();
              // Focus the input
              queryInput.focus();
            } else if (e.key === 'Escape' && document.activeElement === queryInput) {
              queryInput.blur();
            }
          });
        }
        init();
      </script>
    </body>
    
    </html>
    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8">
      <title>ZingSoft Demo</title>
      <script src="https://cdn.zingchart.com/zingchart.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.2/marked.min.js"></script>
    </head>
    
    <body>
      <header>
        <h1>Analyze Your ZingChart with AI</h1>
      </header>
      <main>
        <section id="sectionChart">
          <div id="zingChart"></div>
          <div id="chartQuery">
            <label for="query">Ask AI a Question About the Chart</label>
            <form id="queryForm">
              <input id="query" name="query" type="text" placeholder="Your query here..." autocomplete="off" />
              <button>ASK</button>
            </form>
          </div>
        </section>
        <section id="sectionOutput">
          <output>
            <div class="loader"></div>
          </output>
          <button>Analyze Chart</button>
        </section>
      </main>
    </body>
    
    </html>
    /* main.css */
    
    html,
    body {
      margin: 0;
      padding: 0;
    }
    
    html,
    body,
    #zingChart {
      height: 100%;
      width: 100%;
    }
    
    body {
      background-color: rgb(118, 118, 118);
      color: white;
      display: grid;
      font-family: sans-serif;
      grid-template-areas:
        'blank1'
        'title'
        'chart'
        'blank2';
      grid-template-rows: 1fr auto auto 1fr;
      justify-content: center;
      row-gap: 1rem;
      padding: 0;
    }
    
    header {
      grid-area: title;
      text-align: center;
    
      h1 {
        filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, 0.6));
      }
    }
    
    main {
      align-items: center;
      column-gap: 2rem;
      display: grid;
      grid-area: chart;
      grid-template-columns: 2fr 1fr;
      height: 100%;
    }
    
    section {
      height: fit-content;
      width: 100%;
    }
    
    #sectionChart {
      align-items: center;
      background-color: #dbdbdb;
      border: 1rem solid #dbdbdb;
      border-radius: 10px;
      box-sizing: border-box;
      color: black;
      display: grid;
      filter: drop-shadow(2px 2px 8px rgba(0, 0, 0, 0.25));
      height: 45.5rem;
    
      #zingChart {
        border-radius: 5px;
        justify-self: flex-end;
        min-height: 38.9rem;
      }
    
      #chartQuery {
        display: grid;
        margin-top: 1rem;
        place-items: center;
        text-align: center;
    
        label[for='query'] {
          margin-bottom: 0.5rem;
        }
    
        #queryForm {
          display: flex;
          max-width: 60ch;
          width: 100%;
    
          #query {
            border: none;
            border-top-left-radius: 5px;
            border-bottom-left-radius: 5px;
            font-size: 1.1rem;
            padding: 0.5ch 1ch;
            width: 100%;
          }
    
          button {
            border: none;
            border-top-right-radius: 5px;
            border-bottom-right-radius: 5px;
            cursor: pointer;
            padding: 0 1.5ch;
          }
    
          button:hover {
            background-color: #e3e3e3;
          }
    
          button:active {
            background-color: #d0d0d0;
          }
        }
      }
    }
    
    #sectionOutput {
      background-color: #dbdbdb;
      border: 1rem solid #dbdbdb;
      border-radius: 10px;
      box-sizing: border-box;
      color: black;
      display: grid;
      grid-template-columns: 1fr;
      grid-template-rows: 1fr auto;
      filter: drop-shadow(2px 2px 8px rgba(0, 0, 0, 0.25));
      height: 45.5rem;
      justify-items: center;
      justify-self: flex-start;
      row-gap: 2ch;
      width: 52ch;
    
      output {
        background-color: white;
        border-radius: 5px;
        box-sizing: border-box;
        display: block;
        overflow-y: scroll;
        padding: 1ch;
        width: 100%;
    
        &> :first-child {
          margin-block-start: 0;
        }
      }
    
      output.error {
        color: red;
        display: grid;
        font-size: 1.5rem;
        place-items: center;
        text-align: center;
      }
    
      output:has(.loader.active) {
        display: grid;
        place-items: center;
      }
    
      button {
        background-color: white;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, 0.35));
        padding: 1ch 3ch;
        width: fit-content;
      }
    
      button:hover {
        filter: drop-shadow(2px 2px 1px rgba(0, 0, 0, 0.2));
      }
    
      button:active {
        filter: none;
      }
    
      .wrapper {
        display: grid;
        justify-items: center;
        row-gap: 1rem;
        width: fit-content;
      }
    }
    
    @media (max-width: 1500px) {
      body {
        grid-template-columns: 1fr;
        padding: 0;
        width: 100%;
      }
    
      main {
        grid-template-columns: 1fr;
        grid-template-rows: auto auto;
        justify-items: center;
        row-gap: 2rem;
      }
    
      main #sectionChart {
        height: 41.8rem;
        width: 90%;
      }
    
      main #sectionChart #zingChart {
        height: auto;
        min-height: 35rem;
        width: 100%;
      }
    
      main #sectionOutput {
        margin-bottom: 2rem;
        justify-self: unset;
        width: 90%;
      }
    }
    
    @media (max-width: 1000px) {
      main #sectionChart {
        height: 31.6rem;
      }
    
      main #sectionChart #zingChart {
        min-height: 25rem;
      }
    }
    
    /*****************************************/
    /***         Loading Animation         ***/
    /*** https://css-loaders.com/colorful/ ***/
    /*****************************************/
    
    /* HTML: <div class="loader"></div> */
    .loader {
      display: none;
    }
    
    .loader.active {
      width: 40px;
      aspect-ratio: 1;
      border-radius: 50%;
      margin-top: -30px;
      display: flex;
      justify-content: center;
      overflow: hidden;
      transform-origin: 50% 116.5%;
      animation: l17 2s infinite linear;
    }
    
    .loader.active:before {
      content: '';
      min-width: 233%;
      height: 233%;
      background: radial-gradient(farthest-side, #6fde99 90%, #0000) top,
        radial-gradient(farthest-side, #61e9d8 90%, #0000) left,
        radial-gradient(farthest-side, #ff9c85 90%, #0000) bottom,
        radial-gradient(farthest-side, #ffe295 90%, #0000) right;
      background-size: 43% 43%;
      background-repeat: no-repeat;
      animation: inherit;
      animation-direction: reverse;
    }
    
    @keyframes l17 {
      100% {
        transform: rotate(360deg);
      }
    }
    // main.js
    /*******************************************/
    /* HELPER FUNCTIONS FOR THIS DEMO - IGNORE */
    /*******************************************/
    var __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
      function adopt(value) {
        return value instanceof P ? value : new P(function(resolve) {
          resolve(value);
        });
      }
      return new(P || (P = Promise))(function(resolve, reject) {
        function fulfilled(value) {
          try {
            step(generator.next(value));
          } catch (e) {
            reject(e);
          }
        }
    
        function rejected(value) {
          try {
            step(generator["throw"](value));
          } catch (e) {
            reject(e);
          }
        }
    
        function step(result) {
          result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
        }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
      });
    };
    /*****************/
    /* PROGRAM START */
    /*****************/
    const SERVER_URL = 'https://app-pmfi2cavkq-uc.a.run.app';
    const ANALYZE_CHART_API = '/analyze-chart';
    const ANALYZE_CHART_QUERY_API = '/analyze-chart-query';
    const CHART_ID = 'zingChart';
    let chartData = {
      warrants: [3, 3, 4, 6, 4, 3, 3, 5, 6, 7, 9, 10],
      orders: [3, 3, 4, 7, 4, 3, 3, 7, 8, 9, 11, 12],
      subpoenas: [3, 3, 4, 6, 4, 3, 3, 9, 10, 12, 13, 14],
      usRequests: [3, 3, 4, 6, 4, 3, 3, 10, 11, 13, 14, 15],
      governmentRequests: [3, 3, 4, 6, 4, 3, 3, 11, 12, 14, 15, 20],
    };
    let chartConfig = {
      type: 'area',
      globals: {
        fontColor: '#333',
        fontFamily: 'sans-serif',
      },
      title: {
        text: 'Government Requests by Volume',
        align: 'center',
        padding: '5px',
      },
      subtitle: {
        text: 'This area graph shows the total number of requests received over time, broken down by request type',
        align: 'center',
        fontColor: '#505050',
        padding: '10px',
      },
      legend: {
        align: 'center',
        border: 'none',
        item: {
          fontSize: '18px',
        },
        layout: '3x2',
        marker: {
          type: 'circle',
        },
        verticalAlign: 'bottom',
      },
      plot: {
        tooltip: {
          text: '%t:<br>%v Requests received between<br>July 2020 & June 2021',
          backgroundColor: '#F7F9FA',
          borderRadius: '4px',
          callout: true,
          fontColor: '#505050',
          fontSize: '18px',
          padding: '20px 35px',
        },
        aspect: 'spline',
      },
      scaleX: {
        labels: [
          'Jul 20',
          'Aug 20',
          'Sep 20',
          'Oct 20',
          'Nov 20',
          'Dec 20',
          'Jan 21',
          'Feb 21',
          'Mar 21',
          'Apr 21',
          'May 21',
          'Jun 21',
        ],
      },
      scaleY: {
        values: '0:16:2',
      },
      series: [{
          text: 'Arrest Warrants',
          values: chartData.warrants,
          alphaArea: 1,
          backgroundColor: '#62E9D8',
          hoverMarker: {
            backgroundColor: '#fff',
            borderColor: '#eee',
            size: '10px',
          },
          lineColor: '#ffffff',
          lineWidth: '3px',
          marker: {
            size: '0px',
          },
          zIndex: 10,
        },
        {
          text: ' Court Orders',
          values: chartData.orders,
          alphaArea: 1,
          backgroundColor: '#70DE99',
          hoverMarker: {
            backgroundColor: '#fff',
            borderColor: '#eee',
            size: '10px',
          },
          lineColor: '#ffffff',
          marker: {
            size: '0px',
          },
          zIndex: 9,
        },
        {
          text: 'Subpoenas',
          values: chartData.subpoenas,
          alphaArea: 1,
          backgroundColor: '#B2DC93',
          hoverMarker: {
            backgroundColor: '#fff',
            borderColor: '#eee',
            size: '10px',
          },
          lineColor: '#ffffff',
          marker: {
            size: '0px',
          },
          zIndex: 8,
        },
        {
          text: 'US Requests',
          values: chartData.usRequests,
          alphaArea: 1,
          backgroundColor: '#FFE295',
          hoverMarker: {
            backgroundColor: '#fff',
            borderColor: '#eee',
            size: '10px',
          },
          lineColor: '#ffffff',
          marker: {
            size: '0px',
          },
          zIndex: 7,
        },
        {
          text: 'Government Requests',
          values: chartData.governmentRequests,
          alphaArea: 1,
          backgroundColor: '#FF9C85',
          hoverMarker: {
            backgroundColor: '#fff',
            borderColor: '#eee',
            size: '10px',
          },
          lineColor: '#ffffff',
          marker: {
            size: '0px',
          },
          zIndex: 6,
        },
      ],
      gui: {
        contextMenu: {
          item: {
            fontColor: 'white',
          },
          customItems: [{
            text: 'Analyze Chart with AI',
            function: 'analyzeWithAI()',
            id: 'analyzeWithAI',
          }, ],
        },
      },
      mediaRules: [{
          maxWidth: 600,
          chart: {
            paddingTop: 30,
          },
        },
        {
          maxWidth: 600,
          subtitle: {
            text: 'This area graph shows the total number of requests\nreceived over time, broken down by request type',
            paddingBottom: 10,
          },
        },
        {
          maxWidth: 500,
          legend: {
            layout: '6x1',
          },
        },
        {
          maxWidth: 400,
          title: {
            text: 'Government Requests\nby Volume',
            paddingBottom: 20,
          },
        },
      ],
    };
    /**
     * Exports a given chart to a jpg image
     * @param {string} chartId The ID of the chart to export the image from
     * @return {string} The base64 encoded image data as a JPG
     */
    function exportChartImage(chartId) {
      return __awaiter(this, void 0, void 0, function*() {
        return new Promise((resolve, reject) => {
          try {
            zingchart.exec(chartId, 'getimagedata', {
              filetype: 'jpg',
              callback: (imagedata) => resolve(imagedata),
            });
          } catch (err) {
            reject(err);
          }
        });
      });
    }
    /**
     * @param {string} chartId The ID of the chart to export the image from
     * @param {string} [query] The query to send along with the chart
     * @return {string} The AI's response of the image
     */
    function analyzeChart(chartId, query) {
      return __awaiter(this, void 0, void 0, function*() {
        // Create the URL and Request Body for the AI request
        let url = SERVER_URL;
        let reqBody = {
          chartImage: yield exportChartImage(chartId)
        };
        // Determine what type of request it is
        if (query) {
          url += ANALYZE_CHART_QUERY_API;
          reqBody.query = query;
        } else {
          url += ANALYZE_CHART_API;
        }
        try {
          let response = yield fetch(url, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(reqBody),
          });
          return yield response.json();
        } catch (err) {
          console.error('Trouble reaching endpoint', err);
        }
      });
    }
    /**
     * Analyze the current chart with AI and then output that analysis to the page.
     * Function exposed to window so it's available to ZingChart.
     * @param {string} [query] Query to send along with chart
     */
    window.analyzeWithAI = function(query) {
      let output = document.querySelector('output');
      output.innerHTML = /* HTML */ `<div class="loader"></div>`;
      output.classList.remove('error');
      let loader = document.querySelector('output > .loader');
      // Create simple loader counter for UX
      loader.classList.add('active');
      // Sends chart off to analyze
      analyzeChart(CHART_ID, query)
        .then((aiAnalysis) => {
          var _a, _b;
          loader.classList.remove('active');
          aiAnalysis = (_b = (_a = aiAnalysis.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content;
          if (!aiAnalysis) {
            output.innerHTML = 'Error: Trouble reaching AI server';
            output.classList.add('error');
          } else {
            let renderedMarkdown = marked.parse(aiAnalysis);
            output.innerHTML = renderedMarkdown;
          }
        })
        .catch((err) => {
          output.innerHTML = 'Error: Trouble reaching AI server';
          output.classList.add('error');
        });
    };
    
    function init() {
      // Render ZingChart
      zingchart.render({
        id: CHART_ID,
        data: chartConfig,
        height: '100%',
        // height: '400',
        width: '100%',
      });
      // Attach event listener to analyze chart on button click
      let analyzeChartBtn = document.querySelector('#sectionOutput button');
      analyzeChartBtn.addEventListener('click', () => analyzeWithAI());
      // Attach event listener to query form
      let queryForm = document.querySelector('form#queryForm');
      let queryInput = document.querySelector('input#query');
      queryForm.addEventListener('submit', (e) => {
        // Prevent the form from submitting
        e.preventDefault();
        // Grab the query out of the submitted form
        let query = new FormData(queryForm).get('query');
        // Analyze the query
        analyzeWithAI(query);
      });
      document.addEventListener('keydown', (e) => {
        if (e.key === '/') {
          // Don't add the / to the input field
          e.preventDefault();
          // Focus the input
          queryInput.focus();
        } else if (e.key === 'Escape' && document.activeElement === queryInput) {
          queryInput.blur();
        }
      });
    }
    init();