From 77371e8f3eea6d38ea2092a3bbdca1d8a190b396 Mon Sep 17 00:00:00 2001 From: Marwin Hochfelsner <50826859+hlfan@users.noreply.github.com> Date: Sat, 10 May 2025 00:50:56 +0200 Subject: [PATCH] Prerender heatmap in partial --- app/assets/config/manifest.js | 1 - app/assets/javascripts/heatmap.js | 163 ++++------- app/assets/stylesheets/common.scss | 31 +- app/controllers/users_controller.rb | 60 ++-- app/views/users/_heatmap.html.erb | 25 ++ app/views/users/show.html.erb | 27 +- config/routes.rb | 2 +- package.json | 1 - test/controllers/users_controller_test.rb | 36 +-- yarn.lock | 334 +--------------------- 10 files changed, 182 insertions(+), 498 deletions(-) create mode 100644 app/views/users/_heatmap.html.erb diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index d47476eaa..3b1f63f8b 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -20,4 +20,3 @@ //= link leaflet/dist/images/marker-icon.png //= link leaflet/dist/images/marker-icon-2x.png //= link leaflet/dist/images/marker-shadow.png -//= link cal-heatmap/dist/cal-heatmap.css diff --git a/app/assets/javascripts/heatmap.js b/app/assets/javascripts/heatmap.js index 831ac4b6e..11a74597f 100644 --- a/app/assets/javascripts/heatmap.js +++ b/app/assets/javascripts/heatmap.js @@ -1,91 +1,67 @@ -//= require d3/dist/d3 -//= require cal-heatmap/dist/cal-heatmap -//= require popper -//= require cal-heatmap/dist/plugins/Tooltip - -/* global CalHeatmap, Tooltip */ -document.addEventListener("DOMContentLoaded", () => { - const heatmapElement = document.querySelector("#cal-heatmap"); - - if (!heatmapElement) { - return; +$(function () { + const heatmap = $(".heatmap").removeClass("d-none").addClass("d-grid"); + const weekInfo = getWeekInfo(); + const maxPerDay = heatmap.data("max-per-day"); + const weekdayLabels = heatmap.find("[data-weekday]"); + let weekColumn = 1; + let previousMonth = null; + + for (const day of weekdayLabels) { + const $day = $(day); + const weekday = $day.data("weekday"); + if (weekday < weekInfo.firstDay % 7) { + $day.insertAfter(weekdayLabels.last()); + } + const weekdayRow = getWeekdayRow(weekday); + if (weekdayRow % 2 === 0) $day.remove(); + $day.css("grid-area", weekdayRow + " / 1"); } - /** @type {{date: string; max_id: number; total_changes: number}[]} */ - const heatmapData = heatmapElement.dataset.heatmap ? JSON.parse(heatmapElement.dataset.heatmap) : []; - const displayName = heatmapElement.dataset.displayName; - const colorScheme = document.documentElement.getAttribute("data-bs-theme") ?? "auto"; - const rangeColorsDark = ["#14432a", "#4dd05a"]; - const rangeColorsLight = ["#4dd05a", "#14432a"]; - const startDate = new Date(Date.now() - (365 * 24 * 60 * 60 * 1000)); - - const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); - - let cal = new CalHeatmap(); - let currentTheme = getTheme(); - - function renderHeatmap() { - cal.destroy(); - cal = new CalHeatmap(); - - cal.paint({ - itemSelector: "#cal-heatmap", - theme: currentTheme, - domain: { - type: "month", - gutter: 4, - label: { - text: (timestamp) => new Date(timestamp).toLocaleString(OSM.i18n.locale, { timeZone: "UTC", month: "short" }), - position: "top", - textAlign: "middle" - }, - dynamicDimension: true - }, - subDomain: { - type: "ghDay", - radius: 2, - width: 11, - height: 11, - gutter: 4 - }, - date: { - start: startDate - }, - range: 13, - data: { - source: heatmapData, - type: "json", - x: "date", - y: "total_changes" - }, - scale: { - color: { - type: "sqrt", - range: currentTheme === "dark" ? rangeColorsDark : rangeColorsLight, - domain: [0, Math.max(0, ...heatmapData.map(d => d.total_changes))] - } + for (const day of heatmap.find("[data-date]")) { + const $day = $(day); + const date = new Date($day.data("date")); + if (date.getUTCDay() === weekInfo.firstDay) { + weekColumn++; + const currentMonth = getMonthOfThisWeek(date); + if (previousMonth === null) { + previousMonth = currentMonth; + heatmap.find(`[data-month]:has( ~ [data-month="${previousMonth}"])`).remove(); + heatmap.find("[data-month]").first().css("grid-column-start", 2); } - }, [ - [Tooltip, { - text: (date, value) => getTooltipText(date, value) - }] - ]); - - cal.on("mouseover", (event, timestamp, value) => { - if (!displayName || !value) return; - if (event.target.parentElement.nodeName === "a") return; + if (previousMonth % 12 !== currentMonth % 12) { + heatmap.find(`[data-month="${previousMonth}"]`).css("grid-column-end", weekColumn); + previousMonth++; + heatmap.find(`[data-month="${previousMonth}"]`).css("grid-column-start", weekColumn); + } + } + if (weekColumn === 1) { + $day.remove(); + continue; + } + const count = $day.data("count") ?? 0; + const tooltipText = getTooltipText(date, count); + $day + .css("grid-area", getWeekdayRow(date.getUTCDay()) + " / " + weekColumn) + .attr("aria-label", tooltipText) + .tooltip({ + title: tooltipText, + customClass: "wide", + delay: { show: 0, hide: 0 } + }) + .find("div") + .css("opacity", Math.sqrt(count / maxPerDay)); + } + heatmap.find(`[data-month="${previousMonth}"] ~ [data-month]`).remove(); + heatmap.find("[data-month]").last().css("grid-column-end", weekColumn + 1); - for (const { date, max_id } of heatmapData) { - if (!max_id) continue; - if (timestamp !== Date.parse(date)) continue; + function getMonthOfThisWeek(date) { + const nextDate = new Date(date); + nextDate.setUTCDate(date.getUTCDate() + weekInfo.minimalDays - 1); + return nextDate.getUTCMonth() + 1; + } - const params = new URLSearchParams({ before: max_id + 1 }); - const a = document.createElementNS("http://www.w3.org/2000/svg", "a"); - a.setAttribute("href", `/user/${encodeURIComponent(displayName)}/history?${params}`); - $(event.target).wrap(a); - break; - } - }); + function getWeekdayRow(weekday) { + return ((weekday - weekInfo.firstDay + 7) % 7) + 2; } function getTooltipText(date, value) { @@ -98,22 +74,9 @@ document.addEventListener("DOMContentLoaded", () => { return OSM.i18n.t("javascripts.heatmap.tooltip.no_contributions", { date: localizedDate }); } - function getTheme() { - if (colorScheme === "auto") { - return mediaQuery.matches ? "dark" : "light"; - } - - return colorScheme; - } - - if (colorScheme === "auto") { - mediaQuery.addEventListener("change", (e) => { - currentTheme = e.matches ? "dark" : "light"; - renderHeatmap(); - }); + function getWeekInfo() { + const weekInfo = { firstDay: 1, minimalDays: 4 }; // ISO 8601 + const locale = new Intl.Locale(OSM.i18n.locale); + return { ...weekInfo, ...locale.weekInfo, ...locale.getWeekInfo?.() }; } - - renderHeatmap(); }); - - diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 1d4f16b14..23d9ee25b 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -960,6 +960,33 @@ img.trace_image { } } -.heatmap-wrapper { - height: 130px; +/* Rules for the heatmap */ + +.heatmap { + grid-template-columns: auto; + grid-auto-columns: minmax(1em, 1fr); + grid-template-rows: auto; + grid-auto-rows: minmax(1em, 1fr); + font-size: x-small; + gap: 0.3em; + [data-date] { + aspect-ratio: 1; + background-color: var(--bs-success-border-subtle); + @extend .h-100, .overflow-hidden; + border-radius: 25%; + div { + background-color: var(--bs-success-text-emphasis); + @extend .h-100; + } + &:empty { + @extend .bg-body-secondary, .bg-opacity-75; + } + &:hover { + border: solid 1px #8884; + } + } +} + +.tooltip.wide { + --bs-tooltip-max-width: none; } diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index bcd9031b4..e079da854 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -24,27 +24,53 @@ class UsersController < ApplicationController if @user && (@user.visible? || current_user&.administrator?) @title = @user.display_name - @heatmap_data = Rails.cache.fetch("heatmap_data_with_ids_user_#{@user.id}", :expires_in => 1.day) do + @heatmap_data = Rails.cache.fetch("heatmap_data_of_user_#{@user.id}", :expires_in => 1.day) do one_year_ago = 1.year.ago.beginning_of_day today = Time.zone.now.end_of_day - Changeset - .where(:user_id => @user.id) - .where(:created_at => one_year_ago..today) - .where(:num_changes => 1..) - .group("date_trunc('day', created_at)") - .select("date_trunc('day', created_at) AS date, SUM(num_changes) AS total_changes, MAX(id) AS max_id") - .order("date") - .map do |changeset| - { - :date => changeset.date.to_date.to_s, - :total_changes => changeset.total_changes.to_i, - :max_id => changeset.max_id - } - end - end + mapped = Changeset + .where(:user_id => @user.id) + .where(:created_at => one_year_ago..today) + .where(:num_changes => 1..) + .group("date_trunc('day', created_at)") + .select("date_trunc('day', created_at) AS date, SUM(num_changes) AS total_changes, MAX(id) AS max_id") + .order("date") + .map do |changeset| + { + :date => changeset.date.to_date, + :total_changes => changeset.total_changes.to_i, + :max_id => changeset.max_id + } + end + + indexed = mapped.index_by { |entry| entry[:date] } + + # Pad the start by one week to ensure the heatmap can start on the first day of the week + all_days = ((1.year.ago - 1.week).beginning_of_day.to_date..today.to_date).map do |date| + indexed[date] || { :date => date, :total_changes => 0 } + end + + # Get unique months with repeating months and count into the next year with numbers over 12 + month_offset = 0 + months = ((1.year.ago - 2.weeks).beginning_of_day.to_date..(today + 1.week).to_date) + .map(&:month) + .chunk_while { |before, after| before == after } + .map(&:first) + .map do |month| + month_offset += 12 if month == 1 + month + month_offset + end - @changes_count = @heatmap_data.sum { |entry| entry[:total_changes] } + total = mapped.sum { |entry| entry[:total_changes] } + max_per_day = mapped.map { |entry| entry[:total_changes] }.max + + { + :days => all_days, + :months => months, + :total => total, + :max_per_day => max_per_day + } + end else render_unknown_user params[:display_name] end diff --git a/app/views/users/_heatmap.html.erb b/app/views/users/_heatmap.html.erb new file mode 100644 index 000000000..c86b1c4a4 --- /dev/null +++ b/app/views/users/_heatmap.html.erb @@ -0,0 +1,25 @@ +

+ <%= t("users.show.contributions", :count => data[:total]) %> +

+
+
+
+ + <% data[:months].each do |month| %> + <%= t("date.abbr_month_names")[((month - 1) % 12) + 1] %> + <% end %> + + <% (0..6).each do |day| %> + <%= t("date.abbr_day_names")[day] %> + <% end %> + + <% data[:days].each do |day| %> + <% if day[:total_changes] == 0 %> + + <% else %> +
+ <% end %> + <% end %> +
+
+
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 2dbe72c34..112bdd894 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,5 +1,4 @@ <% content_for :head do %> - <%= stylesheet_link_tag "cal-heatmap/dist/cal-heatmap" %> <%= javascript_include_tag "heatmap" %> <% end %> <% content_for :heading do %> @@ -247,30 +246,8 @@
<%= @user.description.to_html %>
-<% if @heatmap_data.present? %> -

- <%= t(".contributions", :count => @changes_count) %> -

-
-
-
- -
    -
  •  
  • -
  • <%= t("date.abbr_day_names")[1] %>
  • -
  •  
  • -
  • <%= t("date.abbr_day_names")[3] %>
  • -
  •  
  • -
  • <%= t("date.abbr_day_names")[5] %>
  • -
  •  
  • -
- -
-
-
-
-
+<% if @heatmap_data[:total].positive? %> + <%= render :partial => "heatmap", :locals => { :data => @heatmap_data } %> <% end %> <% if current_user and @user.id == current_user.id %> diff --git a/config/routes.rb b/config/routes.rb index 1e136d9e3..168b8dd08 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -157,7 +157,7 @@ OpenStreetMap::Application.routes.draw do resources :notes, :path => "note", :id => /\d+/, :only => [:show, :new] - get "/user/:display_name/history" => "changesets#index" + get "/user/:display_name/history" => "changesets#index", :as => :user_history get "/user/:display_name/history/feed" => "changesets#feed", :defaults => { :format => :atom } get "/user/:display_name/notes" => "notes#index", :as => :user_notes get "/history/friends" => "changesets#index", :friends => true, :as => "friend_changesets", :defaults => { :format => :html } diff --git a/package.json b/package.json index 633c8b53b..46cae3ae6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "openstreetmap", "private": true, "dependencies": { - "cal-heatmap": "^4.2.4", "i18n-js": "^4.5.1", "jquery-simulate": "^1.0.2", "js-cookie": "^3.0.0", diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 0dc06a7f9..305932f97 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -339,11 +339,14 @@ class UsersControllerTest < ActionDispatch::IntegrationTest get user_path(user.display_name) assert_response :success # The data should not be empty - assert_not_nil assigns(:heatmap_data) - heatmap_data = assigns(:heatmap_data) + assert_not_nil heatmap_data + assert_predicate heatmap_data[:days], :any? # The data should be in the right format - assert(heatmap_data.all? { |entry| entry[:date] && entry[:total_changes] }, "Heatmap data should have :date and :total_changes keys") + assert(heatmap_data[:days].all? { |entry| entry[:date] && entry[:total_changes] }, "Heatmap data should have :date and :total_changes keys") + assert_predicate heatmap_data[:months], :any? + assert_equal 30, heatmap_data[:total] + assert_equal 20, heatmap_data[:max_per_day] end def test_show_heatmap_data_caching @@ -361,10 +364,10 @@ class UsersControllerTest < ActionDispatch::IntegrationTest get user_path(user.display_name) first_response_data = assigns(:heatmap_data) assert_not_nil first_response_data, "Expected heatmap data to be assigned on the first request" - assert_equal 1, first_response_data.size, "Expected one entry in the heatmap data" + assert_equal 1, first_response_data[:days].count { |day| day[:total_changes].positive? }, "Expected one entry in the heatmap data" # Inspect cache after the first request - cached_data = Rails.cache.read("heatmap_data_with_ids_user_#{user.id}") + cached_data = Rails.cache.read("heatmap_data_of_user_#{user.id}") assert_equal first_response_data, cached_data, "Expected the cache to contain the first response data" # Add a new changeset to the database @@ -383,7 +386,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest third_response_data = assigns(:heatmap_data) # Ensure the new entry is now included - assert_equal 2, third_response_data.size, "Expected two entries in the heatmap data after clearing the cache" + assert_equal 2, third_response_data[:days].count { |day| day[:total_changes].positive? }, "Expected two entries in the heatmap data after clearing the cache" # Reset caching config to defaults Rails.cache.clear @@ -395,8 +398,8 @@ class UsersControllerTest < ActionDispatch::IntegrationTest get user_path(user.display_name) assert_response :success - # There should be no entries in heatmap data - assert_empty assigns(:heatmap_data) + assert_empty(assigns(:heatmap_data)[:days].select { |e| e[:total_changes].positive? }) + assert_select ".heatmap", :count => 0 end def test_heatmap_rendering @@ -404,7 +407,7 @@ class UsersControllerTest < ActionDispatch::IntegrationTest user_without_changesets = create(:user) get user_path(user_without_changesets) assert_response :success - assert_select "div#cal-heatmap", 0 + assert_select ".heatmap", 0 # Test user with changesets user_with_changesets = create(:user) @@ -413,15 +416,12 @@ class UsersControllerTest < ActionDispatch::IntegrationTest changeset11 = create(:changeset, :user => user_with_changesets, :created_at => 3.months.ago.beginning_of_day, :num_changes => 11) get user_path(user_with_changesets) assert_response :success - assert_select "div#cal-heatmap[data-heatmap]" do |elements| - # Check the data-heatmap attribute is present and contains expected JSON - heatmap_data = JSON.parse(elements.first["data-heatmap"]) - expected_data = [ - { "date" => 4.months.ago.to_date.to_s, "total_changes" => 39, "max_id" => changeset39.id }, - { "date" => 3.months.ago.to_date.to_s, "total_changes" => 16, "max_id" => changeset11.id } - ] - assert_equal expected_data, heatmap_data - end + assert_select ".heatmap a", 2 + + history_path = user_history_path(user_with_changesets) + assert_select ".heatmap a[data-date='#{4.months.ago.to_date}'][data-count='39'][href='#{history_path}?before=#{changeset39.id + 1}']" + assert_select ".heatmap a[data-date='#{3.months.ago.to_date}'][data-count='16'][href='#{history_path}?before=#{changeset11.id + 1}']" + assert_select ".heatmap [data-date='#{5.months.ago.to_date}']:not([data-count])" end def test_heatmap_headline_changset_zero diff --git a/yarn.lock b/yarn.lock index 29ecdf09f..85282b34c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -117,20 +117,6 @@ zod "^3.23.8" zod-to-json-schema "^3.24.1" -"@observablehq/plot@^0.6.0": - version "0.6.17" - resolved "https://registry.yarnpkg.com/@observablehq/plot/-/plot-0.6.17.tgz#9a43f785e89e6d4413701e2b24d2ec8cc2700e02" - integrity sha512-/qaXP/7mc4MUS0s4cPPFASDRjtsWp85/TbfsciqDgU1HwYixbSbbytNuInD8AcTYC3xaxACgVX06agdfQy9W+g== - dependencies: - d3 "^7.9.0" - interval-tree-1d "^1.0.0" - isoformat "^0.2.0" - -"@popperjs/core@^2.11.6": - version "2.11.8" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" - integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== - "@stylistic/eslint-plugin-js@^4.0.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.2.0.tgz#30536fd35dd6aba08c1e234fe37bf66831c6e989" @@ -223,11 +209,6 @@ bignumber.js@*: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== -binary-search-bounds@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/binary-search-bounds/-/binary-search-bounds-2.0.5.tgz#125e5bd399882f71e6660d4bf1186384e989fba7" - integrity sha512-H0ea4Fd3lS1+sTEB2TgcLoK21lLhwEJzlQv3IN47pJS976Gx4zoWe0ak3q+uYh60ppQxg9F16Ri4tS1sfD4+jA== - body-parser@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.0.tgz#f7a9656de305249a715b549b7b8fd1ab9dfddcfa" @@ -256,21 +237,6 @@ bytes@3.1.2, bytes@^3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cal-heatmap@^4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/cal-heatmap/-/cal-heatmap-4.2.4.tgz#0c35616be0dc09df64e1163a804231d8d3f9feaa" - integrity sha512-TTNoQTRxHXrttOEbkraKv9vy2VpfQIwVLQJTlAfcBusQK9qrBL/UBO+WloAxv2yrR+P8URA2cuXEdc5iztER9g== - dependencies: - "@observablehq/plot" "^0.6.0" - "@popperjs/core" "^2.11.6" - d3-color "^3.1.0" - d3-fetch "^3.0.1" - d3-selection "^3.0.0" - d3-transition "^3.0.1" - dayjs "^1.11.7" - eventemitter3 "^5.0.0" - lodash-es "^4.17.21" - call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" @@ -312,11 +278,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -commander@7: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -361,255 +322,6 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" - integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== - dependencies: - internmap "1 - 2" - -d3-axis@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" - integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== - -d3-brush@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" - integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== - dependencies: - d3-dispatch "1 - 3" - d3-drag "2 - 3" - d3-interpolate "1 - 3" - d3-selection "3" - d3-transition "3" - -d3-chord@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" - integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== - dependencies: - d3-path "1 - 3" - -"d3-color@1 - 3", d3-color@3, d3-color@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" - integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== - -d3-contour@4: - version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" - integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== - dependencies: - d3-array "^3.2.0" - -d3-delaunay@6: - version "6.0.4" - resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" - integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== - dependencies: - delaunator "5" - -"d3-dispatch@1 - 3", d3-dispatch@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" - integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== - -"d3-drag@2 - 3", d3-drag@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" - integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== - dependencies: - d3-dispatch "1 - 3" - d3-selection "3" - -"d3-dsv@1 - 3", d3-dsv@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" - integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== - dependencies: - commander "7" - iconv-lite "0.6" - rw "1" - -"d3-ease@1 - 3", d3-ease@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" - integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== - -d3-fetch@3, d3-fetch@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" - integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== - dependencies: - d3-dsv "1 - 3" - -d3-force@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" - integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== - dependencies: - d3-dispatch "1 - 3" - d3-quadtree "1 - 3" - d3-timer "1 - 3" - -"d3-format@1 - 3", d3-format@3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" - integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== - -d3-geo@3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" - integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== - dependencies: - d3-array "2.5.0 - 3" - -d3-hierarchy@3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" - integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== - -"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" - integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== - dependencies: - d3-color "1 - 3" - -"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" - integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== - -d3-polygon@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" - integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== - -"d3-quadtree@1 - 3", d3-quadtree@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" - integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== - -d3-random@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" - integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== - -d3-scale-chromatic@3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" - integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== - dependencies: - d3-color "1 - 3" - d3-interpolate "1 - 3" - -d3-scale@4: - version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" - integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== - dependencies: - d3-array "2.10.0 - 3" - d3-format "1 - 3" - d3-interpolate "1.2.0 - 3" - d3-time "2.1.1 - 3" - d3-time-format "2 - 4" - -"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" - integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== - -d3-shape@3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" - integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== - dependencies: - d3-path "^3.1.0" - -"d3-time-format@2 - 4", d3-time-format@4: - version "4.1.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" - integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== - dependencies: - d3-time "1 - 3" - -"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" - integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== - dependencies: - d3-array "2 - 3" - -"d3-timer@1 - 3", d3-timer@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" - integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== - -"d3-transition@2 - 3", d3-transition@3, d3-transition@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" - integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== - dependencies: - d3-color "1 - 3" - d3-dispatch "1 - 3" - d3-ease "1 - 3" - d3-interpolate "1 - 3" - d3-timer "1 - 3" - -d3-zoom@3: - version "3.0.0" - resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" - integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== - dependencies: - d3-dispatch "1 - 3" - d3-drag "2 - 3" - d3-interpolate "1 - 3" - d3-selection "2 - 3" - d3-transition "2 - 3" - -d3@^7.9.0: - version "7.9.0" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" - integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== - dependencies: - d3-array "3" - d3-axis "3" - d3-brush "3" - d3-chord "3" - d3-color "3" - d3-contour "4" - d3-delaunay "6" - d3-dispatch "3" - d3-drag "3" - d3-dsv "3" - d3-ease "3" - d3-fetch "3" - d3-force "3" - d3-format "3" - d3-geo "3" - d3-hierarchy "3" - d3-interpolate "3" - d3-path "3" - d3-polygon "3" - d3-quadtree "3" - d3-random "3" - d3-scale "4" - d3-scale-chromatic "3" - d3-selection "3" - d3-shape "3" - d3-time "3" - d3-time-format "4" - d3-timer "3" - d3-transition "3" - d3-zoom "3" - -dayjs@^1.11.7: - version "1.11.13" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" - integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== - debug@^4.3.1, debug@^4.3.2, debug@^4.3.5, debug@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" @@ -622,13 +334,6 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -delaunator@5: - version "5.0.1" - resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" - integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== - dependencies: - robust-predicates "^3.0.2" - depd@2.0.0, depd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -794,11 +499,6 @@ etag@^1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -eventemitter3@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - eventsource-parser@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.1.tgz#5e358dba9a55ba64ca90da883c4ca35bd82467bd" @@ -997,7 +697,7 @@ i18n-js@^4.5.1: lodash "*" make-plural "*" -iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -1027,18 +727,6 @@ inherits@2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -"internmap@1 - 2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" - integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== - -interval-tree-1d@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/interval-tree-1d/-/interval-tree-1d-1.0.4.tgz#b44f657de7ddae69ea3f98e0a9ad4bb046b07d11" - integrity sha512-wY8QJH+6wNI0uh4pDQzMvl+478Qh7Rl4qLmqiluxALlNvl+I+o5x38Pw3/z7mDPTPS1dQalZJXsmbvxx5gclhQ== - dependencies: - binary-search-bounds "^2.0.0" - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -1066,11 +754,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isoformat@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/isoformat/-/isoformat-0.2.1.tgz#2526344a4276a101b2881848dc337d1d2ae74494" - integrity sha512-tFLRAygk9NqrRPhJSnNGh7g7oaVWDwR0wKh/GM2LgmPa50Eg4UfyaCO4I8k6EqJHl1/uh2RAD6g06n5ygEnrjQ== - jquery-simulate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/jquery-simulate/-/jquery-simulate-1.0.2.tgz#2174b859b75123a0ac6d8ab3a9a6fb4ad7e82898" @@ -1135,11 +818,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash-es@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -1338,11 +1016,6 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -robust-predicates@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" - integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== - router@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" @@ -1354,11 +1027,6 @@ router@^2.2.0: parseurl "^1.3.3" path-to-regexp "^8.0.0" -rw@1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" - integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== - safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" -- 2.39.5