如何计算总销售额并使显示的总计动态?

How to calculate the total sales and make the displayed totals dynamic?

我需要向卖家和城市显示我的数据的总销售额。但是,总计中显示的值必须是动态的。

P.S. 我的 table 允许用户 select 行,这是因为值是根据每个 select离子制成。 (table 之间的这种活力已经在完美地发挥作用)。

Total 的初始状态是用所有卖家和城市的总和呈现的,下面我显示了 tables 及其各自的总和,但是,如图所示,这些值是静态的而不是动态的。

我需要它是 diahanic 因为如果我 select 例如卖家 João LuisMiguel,总计中显示的值必须是这两个的总和卖家。

我刚才问了一个关于 的问题,一位朋友 (@Gordon) 在 DC.js 的疑惑中帮助了我很多,Crossfilter 告诉我使用 crossfilter.add() .不幸的是我不能按照建议的方式去做,所以我又来了几乎相同的问题(对不起我的愚蠢)。

var composite = dc.compositeChart('#composite');
var vendedorTable = dc.dataTable("#vendedores");
var citiesTable = dc.dataTable("#cities");

function remove_empty_bins(source_group) {
  return {
    top: function(N) {
      return source_group.all().filter(function(d) {
        return d.value.totalAno > 1e-3 ||
          d.value.totalHomologo > 1e-3;
      }).slice(0, N);
    }
  };
}
var adjustX = 10,
  adjustY = 40;

var url = 'https://gist.githubusercontent.com/bernalvinicius/3cece295bc37de1697e7f83418e7fcc9/raw/a5820379ec6eae76ee792495cc5dd1685c977a73/vendedores.json';
d3.json(url).then(function(data) {

  data.forEach(d =>
    Object.assign(d, {
      mes: d.Month,
      atual: d.Vendas_Ano,
      passado: d.Vendas_Ant
    })
  );
  var cf = crossfilter(data);

  vendedorDim = cf.dimension(function(d) {
    return d.vendnm;
  });
  var vendedorGroup = vendedorDim.group().reduce(reduceAdd, reduceRemove, reduceInitial);

  citiesDim = cf.dimension(function(d) {
    return d.zona;
  });
  var citiesGroup = citiesDim.group().reduce(reduceAdd, reduceRemove, reduceInitial);

  var dim = cf.dimension(dc.pluck('mes')),
    grp1 = dim.group().reduceSum(dc.pluck('atual')),
    grp2 = dim.group().reduceSum(dc.pluck('passado'));
  var minMonth = dim.bottom(1)[0].mes;
  var maxMonth = dim.top(1)[0].mes;

  var all = cf.groupAll();

  dc.dataCount(".dc-data-count")
    .dimension(cf)
    .group(all);

  function reduceAdd(p, v) {
    p.totalAno += +v.Vendas_Ano;
    p.totalHomologo += +v.Vendas_Ant;
    return p;
  }

  function reduceRemove(p, v) {
    p.totalAno -= v.Vendas_Ano;
    p.totalHomologo -= v.Vendas_Ant;
    return p;
  }

  function reduceInitial() {
    return {
      totalAno: 0,
      totalHomologo: 0,
    };
  }

  // Fake Dimension
  rank = function(p) {
    return ""
  };

  // Chart by months
  composite
    .width(window.innerWidth - adjustX)
    .height(300)
    .x(d3.scaleLinear().domain([1, 12]))
    .yAxisLabel("")
    .xAxisLabel("Month")
    .legend(dc.legend().x(500).y(0).itemHeight(13).gap(5))
    .renderHorizontalGridLines(true)
    .compose([
      dc.lineChart(composite)
      .dimension(dim)
      .colors('steelblue')
      .group(grp1, "Currently Year"),
      dc.lineChart(composite)
      .dimension(dim)
      .colors('darkorange')
      .group(grp2, "Last Year")
    ])
    .brushOn(true);

  composite.brush().extent([-0.5, data.length + 1.5])
  composite.extendBrush = function(brushSelection) {
    if (brushSelection) {
      vendedorTable.filter(null);
      vendedorDim.filter(null);
      citiesTable.filter(null);
      citiesDim.filter(null);
      const point = Math.round((brushSelection[0] + brushSelection[1]) / 2);
      return [
        point - 0.5,
        point + 0.5
      ];
    }
  };

  // Sales Table
  vendedorTable.width(500)
    .height(480)
    .dimension(remove_empty_bins(vendedorGroup))
    .group(rank)
    .columns([function(d) {
        return d.key;
      },
      function(d) {
        return Number(Math.round(d.value.totalAno * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      },
      function(d) {
        return Number(Math.round(d.value.totalHomologo * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      }
    ])
    .sortBy(function(d) {
      return d.value.totalAno
    })
    .order(d3.descending)

  // Cities Table
  citiesTable
    .width(500)
    .height(480)
    .dimension(remove_empty_bins(citiesGroup))
    .group(rank)
    .columns([function(d) {
        return d.key;
      },
      function(d) {
        return Number(Math.round(d.value.totalAno * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      },
      function(d) {
        return Number(Math.round(d.value.totalHomologo * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      }
    ])
    .controlsUseVisibility(true)
    .sortBy(function(d) {
      return d.value.totalAno
    })
    .order(d3.descending)

  // Sales click events
  vendedorTable.on('pretransition', function(table) {
    table.selectAll('td.dc-table-column')
      .on('click', function(d) {
        let filters = table.filters().slice();
        if (filters.indexOf(d.key) === -1)
          filters.push(d.key);
        else
          filters = filters.filter(k => k != d.key);
        if (filters.length === 0)
          vendedorDim.filter(null);
        else
          vendedorDim.filterFunction(function(d) {
            return filters.indexOf(d) !== -1;
          })
        table.replaceFilter([filters]);

        citiesTable.filter(null);
        citiesDim.filter(null);
        composite.filter(null);

        dc.redrawAll();
      });
    let filters = table.filters();
    table.selectAll('tr.dc-table-row')
      .classed('sel-rows', d => filters.indexOf(d.key) !== -1);
  });

  // Cities click events
  citiesTable.on('pretransition', function(table) {
    table.selectAll('td.dc-table-column')
      .on('click', function(d) {
        let filters = table.filters().slice();
        if (filters.indexOf(d.key) === -1)
          filters.push(d.key);
        else
          filters = filters.filter(k => k != d.key);
        if (filters.length === 0)
          citiesDim.filter(null);
        else
          citiesDim.filterFunction(function(d) {
            return filters.indexOf(d) !== -1;
          })
        table.replaceFilter([filters]);

        vendedorTable.filter(null);
        vendedorDim.filter(null);
        composite.filter(null);

        dc.redrawAll();
      });
    let filters = table.filters();
    table.selectAll('tr.dc-table-row')
      .classed('sel-rows', d => filters.indexOf(d.key) !== -1);
  });


  dc.renderAll();

  // reset functions
  $('#reset').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });

  $('#resetTable').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });

  $('#resetTable2').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });


  /****************************************************************************/
  // Functions to handle responsive
  apply_resizing(composite, adjustX, adjustY, function(composite) {
    composite.legend().x(window.innerWidth - 200);
  });

  var find_query = function() {
    var _map = window.location.search.substr(1).split('&').map(function(a) {
      return a.split('=');
    }).reduce(function(p, v) {
      if (v.length > 1)
        p[v[0]] = decodeURIComponent(v[1].replace(/\+/g, " "));
      else
        p[v[0]] = true;
      return p;
    }, {});
    return function(field) {
      return _map[field] || null;
    };
  }();

  var resizeMode = find_query('resize') || 'widhei';

  function apply_resizing(composite, adjustX, adjustY, onresize) {
    if (resizeMode === 'viewbox') {
      composite
        .width(300)
        .height(200)
        .useViewBoxResizing(true);
      d3.select(composite.anchor()).classed('fullsize', false);
    } else {
      adjustX = adjustX || 0;
      adjustY = adjustY || adjustX || 0;
      composite
        .width(window.innerWidth - adjustX)
        .height(300);
      window.onresize = function() {
        if (onresize) {
          onresize(composite);
        }
        composite
          .width(window.innerWidth - adjustX)
          .height(300);

        if (composite.rescale) {
          composite.rescale();
        }
        composite.redraw();
      };
    }
  }
});
#composite {
  padding: 10px;
}

.dc-table-group {
  visibility: collapse;
}

tr.dc-table-row.sel-rows {
  background-color: lightblue;
}

.brush .custom-brush-handle {
  display: none;
}
<head>
  <!-- favicon -->
  <link rel="shortcut icon" href="https://img.icons8.com/nolan/64/puzzle.png">
  <!-- bootstrap.css -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- bootstrap-theme.css -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- dc.css -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dc/2.1.8/dc.css">
  <!-- jquery.js -->
  <script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
  <!-- bootstrap.js -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  <!-- d3.v5.js -->
  <script src="https://d3js.org/d3.v5.js"></script>
  <!-- crossfilter.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.js"></script>
  <!-- dc.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.js"></script>

  <title>Chart</title>
</head>

<body>
  <div id="composite"></div>
  <div class="container-fluid">
    <div class="row content">
      <div class="col-md-12">
        <div class="col-md-6">
          <table class="table" id="totaisVendedores">
            <thead>
              <tr>
                <th>Total Sales</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
        <div class="col-md-6">
          <table class="table" id="totaisCities">
            <thead>
              <tr>
                <th>Total City</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
        <div class="col-md-6">
          <table class="table" id="vendedores">
            <thead>
              <tr>
                <th>Sales</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
        <div class="col-md-6">
          <table class="table" id="cities">
            <thead>
              <tr>
                <th>City</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
      </div>
    </div>
  </div>
</body>

抱歉,我完全误读了你的最后一个问题。很高兴你接受了答案,但你真的不必那样做。 :)

为了添加总计,您可以在 table

中添加 tfoot 部分
            <tfoot>
              <tr>
                <th>TOTAL</th>
                <th id="total-current-year"></th>
                <th id="total-last-year"></th>
              </tr>
            </tfoot>

并用 NumberDisplay widget 驱动它。

  const totalCYDisplay = dc.numberDisplay('#total-current-year')
    .group(totalAll)
    .valueAccessor(d => d.totalAno)

default formatting is .2s but numberDisplay accepts any d3-format instance.

不喜欢动画的可以设置.transitionDuration(0)

var composite = dc.compositeChart('#composite');
var vendedorTable = dc.dataTable("#vendedores");
var citiesTable = dc.dataTable("#cities");

function remove_empty_bins(source_group) {
  return {
    top: function(N) {
      return source_group.all().filter(function(d) {
        return d.value.totalAno > 1e-3 ||
          d.value.totalHomologo > 1e-3;
      }).slice(0, N);
    }
  };
}
var adjustX = 10,
  adjustY = 40;

var url = 'https://gist.githubusercontent.com/bernalvinicius/3cece295bc37de1697e7f83418e7fcc9/raw/a5820379ec6eae76ee792495cc5dd1685c977a73/vendedores.json';
d3.json(url).then(function(data) {

  data.forEach(d =>
    Object.assign(d, {
      mes: d.Month,
      atual: d.Vendas_Ano,
      passado: d.Vendas_Ant
    })
  );
  var cf = crossfilter(data);

  vendedorDim = cf.dimension(function(d) {
    return d.vendnm;
  });
  var vendedorGroup = vendedorDim.group().reduce(reduceAdd, reduceRemove, reduceInitial);

  citiesDim = cf.dimension(function(d) {
    return d.zona;
  });
  var citiesGroup = citiesDim.group().reduce(reduceAdd, reduceRemove, reduceInitial);
  const totalAll = cf.groupAll()
    .reduce(reduceAdd, reduceRemove, reduceInitial);
debugger;

  var dim = cf.dimension(dc.pluck('mes')),
    grp1 = dim.group().reduceSum(dc.pluck('atual')),
    grp2 = dim.group().reduceSum(dc.pluck('passado'));
  var minMonth = dim.bottom(1)[0].mes;
  var maxMonth = dim.top(1)[0].mes;

  var all = cf.groupAll();

  dc.dataCount(".dc-data-count")
    .dimension(cf)
    .group(all);

  function reduceAdd(p, v) {
    p.totalAno += +v.Vendas_Ano;
    p.totalHomologo += +v.Vendas_Ant;
    return p;
  }

  function reduceRemove(p, v) {
    p.totalAno -= v.Vendas_Ano;
    p.totalHomologo -= v.Vendas_Ant;
    return p;
  }

  function reduceInitial() {
    return {
      totalAno: 0,
      totalHomologo: 0,
    };
  }

  // Fake Dimension
  rank = function(p) {
    return ""
  };

  // Chart by months
  composite
    .width(window.innerWidth - adjustX)
    .height(300)
    .x(d3.scaleLinear().domain([1, 12]))
    .yAxisLabel("")
    .xAxisLabel("Month")
    .legend(dc.legend().x(500).y(0).itemHeight(13).gap(5))
    .renderHorizontalGridLines(true)
    .compose([
      dc.lineChart(composite)
      .dimension(dim)
      .colors('steelblue')
      .group(grp1, "Currently Year"),
      dc.lineChart(composite)
      .dimension(dim)
      .colors('darkorange')
      .group(grp2, "Last Year")
    ])
    .brushOn(true);

  composite.brush().extent([-0.5, data.length + 1.5])
  composite.extendBrush = function(brushSelection) {
    if (brushSelection) {
      vendedorTable.filter(null);
      vendedorDim.filter(null);
      citiesTable.filter(null);
      citiesDim.filter(null);
      const point = Math.round((brushSelection[0] + brushSelection[1]) / 2);
      return [
        point - 0.5,
        point + 0.5
      ];
    }
  };

  // Sales Table
  vendedorTable.width(500)
    .height(480)
    .dimension(remove_empty_bins(vendedorGroup))
    .group(rank)
    .columns([function(d) {
        return d.key;
      },
      function(d) {
        return Number(Math.round(d.value.totalAno * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      },
      function(d) {
        return Number(Math.round(d.value.totalHomologo * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      }
    ])
    .sortBy(function(d) {
      return d.value.totalAno
    })
    .order(d3.descending)

  // vendedors totals
  const totalCYDisplay = dc.numberDisplay('#total-current-year')
    .group(totalAll)
    .valueAccessor(d => d.totalAno)
  const totalLYDisplay = dc.numberDisplay('#total-last-year')
    .group(totalAll)
    .valueAccessor(d => d.totalHomologo)

  // Cities Table
  citiesTable
    .width(500)
    .height(480)
    .dimension(remove_empty_bins(citiesGroup))
    .group(rank)
    .columns([function(d) {
        return d.key;
      },
      function(d) {
        return Number(Math.round(d.value.totalAno * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      },
      function(d) {
        return Number(Math.round(d.value.totalHomologo * 100) / 100).toLocaleString("es-ES", {
          minimumFractionDigits: 2
        }) + '€';
      }
    ])
    .controlsUseVisibility(true)
    .sortBy(function(d) {
      return d.value.totalAno
    })
    .order(d3.descending)

  // Sales click events
  vendedorTable.on('pretransition', function(table) {
    table.selectAll('td.dc-table-column')
      .on('click', function(d) {
        let filters = table.filters().slice();
        if (filters.indexOf(d.key) === -1)
          filters.push(d.key);
        else
          filters = filters.filter(k => k != d.key);
        if (filters.length === 0)
          vendedorDim.filter(null);
        else
          vendedorDim.filterFunction(function(d) {
            return filters.indexOf(d) !== -1;
          })
        table.replaceFilter([filters]);

        citiesTable.filter(null);
        citiesDim.filter(null);
        composite.filter(null);

        dc.redrawAll();
      });
    let filters = table.filters();
    table.selectAll('tr.dc-table-row')
      .classed('sel-rows', d => filters.indexOf(d.key) !== -1);
  });

  // Cities click events
  citiesTable.on('pretransition', function(table) {
    table.selectAll('td.dc-table-column')
      .on('click', function(d) {
        let filters = table.filters().slice();
        if (filters.indexOf(d.key) === -1)
          filters.push(d.key);
        else
          filters = filters.filter(k => k != d.key);
        if (filters.length === 0)
          citiesDim.filter(null);
        else
          citiesDim.filterFunction(function(d) {
            return filters.indexOf(d) !== -1;
          })
        table.replaceFilter([filters]);

        vendedorTable.filter(null);
        vendedorDim.filter(null);
        composite.filter(null);

        dc.redrawAll();
      });
    let filters = table.filters();
    table.selectAll('tr.dc-table-row')
      .classed('sel-rows', d => filters.indexOf(d.key) !== -1);
  });


  dc.renderAll();

  // reset functions
  $('#reset').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });

  $('#resetTable').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });

  $('#resetTable2').on('click', function() {
    vendedorTable.filter(null);
    vendedorDim.filter(null);
    citiesTable.filter(null);
    citiesDim.filter(null);
    composite.filter(null);

    dc.redrawAll();
  });


  /****************************************************************************/
  // Functions to handle responsive
  apply_resizing(composite, adjustX, adjustY, function(composite) {
    composite.legend().x(window.innerWidth - 200);
  });

  var find_query = function() {
    var _map = window.location.search.substr(1).split('&').map(function(a) {
      return a.split('=');
    }).reduce(function(p, v) {
      if (v.length > 1)
        p[v[0]] = decodeURIComponent(v[1].replace(/\+/g, " "));
      else
        p[v[0]] = true;
      return p;
    }, {});
    return function(field) {
      return _map[field] || null;
    };
  }();

  var resizeMode = find_query('resize') || 'widhei';

  function apply_resizing(composite, adjustX, adjustY, onresize) {
    if (resizeMode === 'viewbox') {
      composite
        .width(300)
        .height(200)
        .useViewBoxResizing(true);
      d3.select(composite.anchor()).classed('fullsize', false);
    } else {
      adjustX = adjustX || 0;
      adjustY = adjustY || adjustX || 0;
      composite
        .width(window.innerWidth - adjustX)
        .height(300);
      window.onresize = function() {
        if (onresize) {
          onresize(composite);
        }
        composite
          .width(window.innerWidth - adjustX)
          .height(300);

        if (composite.rescale) {
          composite.rescale();
        }
        composite.redraw();
      };
    }
  }
});
#composite {
  padding: 10px;
}

.dc-table-group {
  visibility: collapse;
}

tr.dc-table-row.sel-rows {
  background-color: lightblue;
}

.brush .custom-brush-handle {
  display: none;
}
<head>
  <!-- favicon -->
  <link rel="shortcut icon" href="https://img.icons8.com/nolan/64/puzzle.png">
  <!-- bootstrap.css -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- bootstrap-theme.css -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- dc.css -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dc/2.1.8/dc.css">
  <!-- jquery.js -->
  <script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
  <!-- bootstrap.js -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
  <!-- d3.v5.js -->
  <script src="https://d3js.org/d3.v5.js"></script>
  <!-- crossfilter.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter/1.3.12/crossfilter.js"></script>
  <!-- dc.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.js"></script>

  <title>Chart</title>
</head>

<body>
  <div id="composite"></div>
  <div class="container-fluid">
    <div class="row content">
      <div class="col-md-12">
        <div class="col-md-6">
          <table class="table" id="totaisVendedores">
            <thead>
              <tr>
                <th>Total Sales</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
        <div class="col-md-6">
          <table class="table" id="totaisCities">
            <thead>
              <tr>
                <th>Total City</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
        <div class="col-md-6">
          <table class="table" id="vendedores">
            <thead>
              <tr>
                <th>Sales</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
            <tfoot>
              <tr>
                <th>TOTAL</th>
                <th id="total-current-year"></th>
                <th id="total-last-year"></th>
              </tr>
            </tfoot>
          </table>
        </div>
        <div class="col-md-6">
          <table class="table" id="cities">
            <thead>
              <tr>
                <th>City</th>
                <th>Current Year</th>
                <th>Last Year</th>
              </tr>
            </thead>
          </table>
        </div>
      </div>
    </div>
  </div>
</body>