0

I have worked on a couple of examples in carto's tutorials, and I am trying to put them together now. One example was from the carto.js tutorial and the other from a carto-vl.js tutorial. There is also some airship.js, vue.js, and nouislider.js in it.

The problem I'm having when I try to put them together is in the script reference section between the carto-vl and the carto.js.

Here is my web page:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Carto | Map and Chart</title>
  <!-- Include Leaflet -->
    <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
    <link href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" rel="stylesheet">
    <!-- Include CARTO.js -->
    <script src="https://libs.cartocdn.com/carto.js/v4.1.10/carto.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/13.1.0/nouislider.min.js"></script>
    <link href="https://carto.com/developers/carto-js/examples/maps/public/style.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/13.1.0/nouislider.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <!-- this causes the styling to dissapear -->
    <!-- <script src="https://libs.cartocdn.com/carto-vl/v1.0.0/carto-vl.min.js"></script> -->
    <!-- this causes the entire frame to go blank -->
    <!-- <link rel="stylesheet" href="https://libs.cartocdn.com/airship-style/v1.0.3/airship.css"> -->
    <script src="https://libs.cartocdn.com/airship-components/v1.0.3/airship.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
    }

    #sliderContainer {
      position: absolute;
            bottom: 200px;
            left: 150px;
            width: 410px;
      height: 100px;
            padding: 0 15px;
            /* Add styles to match Leaflet UI elements */
            background: rgba(256, 256, 256, 0.7);
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
            border: 1px solid #ddd;
            border-radius: 5px;
            z-index: 800;
    }

    #countSlider {
      position: absolute;
            bottom: 20px;
            left: 15px;
            width: 380px;
            padding: 0 15px;
            /* Add styles to match Leaflet UI elements */
            background: rgba(256, 256, 256, .3);
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
            border: 1px solid #ddd;
            border-radius: 5px;
            z-index: 800;
    }

  </style>
</head>
<body>
  <!-- <div id="map"></div>
  <div id='sliderContainer'>
    <h2>Slider Values</h2>
    <section id="countSlider"></section>
  </div> -->

  <as-responsive-content>
      <main class="as-main">
          <div class="as-map-area">
              <div id="map" style="width:100%;height:100vh;"></div>
              <div id='sliderContainer'>
                <h2>Slider Values</h2>
                <section id="countSlider"></section>
              </div>
              <!-- Panel to render the countries table -->
              <div class="as-map-panels" data-name="Countries">
                  <div class="as-panel as-panel--bottom as-panel--right  as-bg--ui-01" style="height:400px;">
                      <section class="as-box">
                          <h3 class="as-title">Counties in the view</h3>
                          <table id="countries-table" class="as-table as-table--stripped">
                              <tr>
                                  <th>Name</th>
                                  <th>Ticket Count</th>
                              </tr>
                              <tr v-for="country in countries">
                                  <td>{{ country.properties.ticketcounty }}</td>
                                  <td style="text-align:right;">{{ country.properties.ticketcount }} </td>
                              </tr>
                          </table>
                      </section>
                  </div>
              </div>
          </div>
      </main>
  </as-responsive-content>
<script>
  // map options
    var options = {
      center: [33.83333333, -80.86666667],
      zoom: 8
    }

    // create a Leaflet map in our division container with id of 'map'
    var map = L.map('map', options);

    // Leaflet providers base map URL
    var basemap_source =
      'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'

    // Leaflet providers attributes
    var basemap_options = {
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
      bounds: [
        [-85.0511287776, -179.999999975],
        [85.0511287776, 179.999999975]
      ]
    };
    // request some basemap tiles and add to the map
    var tiles = L.tileLayer(basemap_source, basemap_options).addTo(map);

    var client = new carto.Client({
      apiKey: 'key',
      username: 'uname'
    });

    var source = new carto.source.SQL('select * from ticketspercounty_copy');

    // console.log(source);

    var style = new carto.style.CartoCSS(`
      #layer {
          polygon-fill: ramp([ticketcount], (#ffc6c4, #ee919b, #cc607d, #9e3963, #672044), quantiles);
        }
        #layer::outline {
          line-width: 1;
          line-color: #FFFFFF;
          line-opacity: 0.5;
        }
        #layer::labels {
          text-name: [countycounty];
          text-face-name: 'DejaVu Sans Book';
          text-size: 10;
          text-fill: #FFFFFF;
          text-label-position-tolerance: 0;
          text-halo-radius: 1;
          text-halo-fill: #6F808D;
          text-dy: -10;
          text-allow-overlap: true;
          text-placement: point;
          text-placement-type: dummy;
        }
      `);


      var layer = new carto.layer.Layer(source, style);

      client.addLayer(layer);
      client.getLeafletLayer().addTo(map);

      var countSlider = document.getElementById('countSlider');

      noUiSlider.create(countSlider, {
          start: [0, 116000],
          connect: true,
          step: 1000,
          range: {
              'min': 0,
              'max': 116000
          },
          keyboardSupport: true
       });

       // Binding signature
       countSlider.noUiSlider.on('change', function() {
         let ticketCounts = countSlider.noUiSlider.get()
         // console.log(ticketCounts);
         source.setQuery('select * from ticketspercounty_copy where ticketcount >= ' + ticketCounts[0] + ' and ' + ' ticketcount < ' + ticketCounts[1])
       });

       function run() {

           carto.setDefaultAuth({
             apiKey: 'key',
             username: 'uname'
           });

           const tableSource = new carto.source.Dataset('ticketspercounty_copy');
           const viz = new carto.Viz(`
           \@list: viewportFeatures($ticketcounty, $ticketcount)

           color: opacity(red,0.2);
           strokeColor: black;
           `);

           const tableLayer = new carto.Layer('layer', tableSource, viz);
           tableLayer.addTo(map);

           // Vue app to control the table render
           var app = new Vue({
               el: '#countries-table',
               data: {
                   countries: null
               }
           });

           tableLayer.on('updated', () => {
               app.countries = viz.variables.list.value;
           });
       }
       document.querySelector('as-responsive-content').addEventListener('ready', run);

  </script>
</body>
</html>

If you'll notice in the <head> section, I have comments of what happens when I un-comment the reference. The carto-vl.js produces:

Uncaught TypeError: carto.Client is not a constructor

and the airship.js link produces this error:

(index):187 Uncaught TypeError: carto.setDefaultAuth is not a function

I'm kind of flying be the seed of my pants in creating this, so I'm guessing it is a structural issue in my DOM, but I don't understand why the references are cancelling each other out?

Also, the username and the key are the same for both layers.

2

It's not possible to use both carto.js and CARTO VL in the same HTML file using the cdn, because both use the same "carto" namespace. That's the reason why when you comment one of them, you're using the other. If you add both scripts, the "carto" variable will make reference to the last script you've added.

If you want to develop an application with both libraries, it's totally possible, but you'd need a bit more setup than a single index.html file. To avoid complexity, I would use one html file for carto.js and another one for CARTO VL for this particular case.

I suggest you to take a look to this example that uses Webpack to import carto-vl that you can use as start point.

1

It's possible to have both libraries at the same time but it's not straightforward. You need to dynamically load one script, then modify the carto object and then load the other one. After both libraries are loaded you can run the rest of the code. Not easy but doable.

The real question is why do you want both libraries at the same time? CARTO.js is a raster library that works on top of Leaflet. CARTO VL is a vector library that renders on top of Mapbox GL. Mixing both libraries for visualization is not possible.

The only use case for mixing them from the top of my head is using CARTO VL for visualization and CARTO.js for retrieving data via dataviews but, even for that, it's better to use the VL functions that are similar.

  • Thanks for the feedback, Ivan. I am just trying to plug a couple of things together and make it work tbh. I have a choropleth map that I made with carto.js and carto.css and I was trying to add some additional functionality with a dynamic table that updates as the viewport changes . How would you recommend I approach that? Thanks! – Matt Mar 6 at 15:01
  • What data do you want to show in the table? Where does it come from? – Ivan Malagon Mar 7 at 16:30
  • I have a table in carto builder that I am creating a source within carto.js. I have a choropleth map symbolized by county and I would like to basically just show a couple of columns from the carto source as they relate to the map objects (counties) in the view port. – Matt Mar 8 at 1:46
0

Following your conversation with @Iván I'm going to suggest a different answer.

To show alphanumeric data on a web application that already uses CARTO.js the most straightforward way is to use the SQL API to query your table after each map moveend event based in the resulting map bounds limits and using ST_Intersects, you have an example of such WHERE clause here. Here you have some details on how to use SQL API from a CARTO.js or VL application.

To get your data you have any of these options

  • Allow public access to your dataset (depending on your CARTO account your data may be already in this state).
  • Or create an API key to enable SQL API SELECT to this table (you need a ENGINE plan)
  • With ENGINE plan, you can also create a stored procedure that only provides the info you want to expose to the web application and then grant the public_user, more details here.
  • Create a middleware backend that interacts with CARTO private data and exposes your own API with just the data you want to expose (again you need ENGINE for this). An example of this set-up can be found here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.