mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 21:29:38 +02:00
Scaffold out the UI for individual account page (#461)
* Add `AccountBalance` table for account views * Scaffold out account UI * Add D3 line chart scaffolding * Style fixes
This commit is contained in:
parent
0490fda465
commit
3ec9c9b56b
47 changed files with 724 additions and 12 deletions
|
@ -9,7 +9,9 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@account = Current.family.accounts.find(params[:id])
|
# Temporary while dummy data is being used
|
||||||
|
# @account = Current.family.accounts.find(params[:id])
|
||||||
|
@account = sample_account
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -23,9 +25,40 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.require(:account).permit(:name, :accountable_type, :original_balance, :original_currency, :subtype)
|
params.require(:account).permit(:name, :accountable_type, :original_balance, :original_currency, :subtype)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sample_account
|
||||||
|
OpenStruct.new(
|
||||||
|
id: 1,
|
||||||
|
name: "Sample Account",
|
||||||
|
original_balance: BigDecimal("1115181"),
|
||||||
|
original_currency: "USD",
|
||||||
|
converted_balance: BigDecimal("1115181"), # Assuming conversion rate is 1 for simplicity
|
||||||
|
converted_currency: "USD",
|
||||||
|
dollar_change: BigDecimal("1553.43"), # Added dollar change
|
||||||
|
percent_change: BigDecimal("0.9"), # Added percent change
|
||||||
|
subtype: "Checking",
|
||||||
|
accountable_type: "Depository",
|
||||||
|
balances: sample_balances
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sample_balances
|
||||||
|
4.times.map do |i|
|
||||||
|
OpenStruct.new(
|
||||||
|
date: "Feb #{12 + i} 2024",
|
||||||
|
description: "Manually entered",
|
||||||
|
amount: BigDecimal("1000") + (i * BigDecimal("100")),
|
||||||
|
change: i == 3 ? -50 : (i == 2 ? 0 : 100 + (i * 10)),
|
||||||
|
percentage_change: i == 3 ? -5 : (i == 2 ? 0 : 10 + i),
|
||||||
|
icon: i == 3 ? "arrow-down" : (i == 2 ? "minus" : (i.even? ? "arrow-down" : "arrow-up"))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
258
app/javascript/controllers/line_chart_controller.js
Normal file
258
app/javascript/controllers/line_chart_controller.js
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus";
|
||||||
|
import tailwindColors from "@maybe/tailwindcolors";
|
||||||
|
import * as d3 from "d3";
|
||||||
|
|
||||||
|
// Connects to data-controller="line-chart"
|
||||||
|
export default class extends Controller {
|
||||||
|
connect() {
|
||||||
|
this.drawChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawChart() {
|
||||||
|
// TODO: Replace with live data through controller targets
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
date: new Date(2021, 0, 1),
|
||||||
|
value: 985000,
|
||||||
|
formatted: "$985,000",
|
||||||
|
change: { value: "$0", direction: "none", percentage: "0%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 1, 1),
|
||||||
|
value: 990000,
|
||||||
|
formatted: "$990,000",
|
||||||
|
change: { value: "$5,000", direction: "up", percentage: "0.51%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 2, 1),
|
||||||
|
value: 995000,
|
||||||
|
formatted: "$995,000",
|
||||||
|
change: { value: "$5,000", direction: "up", percentage: "0.51%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 3, 1),
|
||||||
|
value: 1000000,
|
||||||
|
formatted: "$1,000,000",
|
||||||
|
change: { value: "$5,000", direction: "up", percentage: "0.50%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 4, 1),
|
||||||
|
value: 1005000,
|
||||||
|
formatted: "$997,000",
|
||||||
|
change: { value: "$3,000", direction: "down", percentage: "-0.30%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 5, 1),
|
||||||
|
value: 1010000,
|
||||||
|
formatted: "$1,010,000",
|
||||||
|
change: { value: "$5,000", direction: "up", percentage: "0.50%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 6, 1),
|
||||||
|
value: 1050000,
|
||||||
|
formatted: "$1,050,000",
|
||||||
|
change: { value: "$40,000", direction: "up", percentage: "3.96%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 7, 1),
|
||||||
|
value: 1080000,
|
||||||
|
formatted: "$1,080,000",
|
||||||
|
change: { value: "$30,000", direction: "up", percentage: "2.86%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 8, 1),
|
||||||
|
value: 1100000,
|
||||||
|
formatted: "$1,100,000",
|
||||||
|
change: { value: "$20,000", direction: "up", percentage: "1.85%" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(2021, 9, 1),
|
||||||
|
value: 1115181,
|
||||||
|
formatted: "$1,115,181",
|
||||||
|
change: { value: "$15,181", direction: "up", percentage: "1.38%" },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialDimensions = {
|
||||||
|
width: document.querySelector("#lineChart").clientWidth,
|
||||||
|
height: document.querySelector("#lineChart").clientHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
const svg = d3
|
||||||
|
.select("#lineChart")
|
||||||
|
.append("svg")
|
||||||
|
.attr("width", initialDimensions.width)
|
||||||
|
.attr("height", initialDimensions.height)
|
||||||
|
.attr("viewBox", [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
initialDimensions.width,
|
||||||
|
initialDimensions.height,
|
||||||
|
])
|
||||||
|
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
|
||||||
|
|
||||||
|
const margin = { top: 20, right: 0, bottom: 30, left: 0 },
|
||||||
|
width = +svg.attr("width") - margin.left - margin.right,
|
||||||
|
height = +svg.attr("height") - margin.top - margin.bottom,
|
||||||
|
g = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("transform", `translate(${margin.left},${margin.top})`);
|
||||||
|
|
||||||
|
// X-Axis
|
||||||
|
const x = d3
|
||||||
|
.scaleTime()
|
||||||
|
.rangeRound([0, width])
|
||||||
|
.domain(d3.extent(data, (d) => d.date));
|
||||||
|
|
||||||
|
const PADDING = 0.15; // 15% padding on top and bottom of data
|
||||||
|
const dataMin = d3.min(data, (d) => d.value);
|
||||||
|
const dataMax = d3.max(data, (d) => d.value);
|
||||||
|
const padding = (dataMax - dataMin) * PADDING;
|
||||||
|
|
||||||
|
// Y-Axis
|
||||||
|
const y = d3
|
||||||
|
.scaleLinear()
|
||||||
|
.rangeRound([height, 0])
|
||||||
|
.domain([dataMin - padding, dataMax + padding]);
|
||||||
|
|
||||||
|
// X-Axis labels
|
||||||
|
g.append("g")
|
||||||
|
.attr("transform", `translate(0,${height})`)
|
||||||
|
.call(
|
||||||
|
d3
|
||||||
|
.axisBottom(x)
|
||||||
|
.tickValues([data[0].date, data[data.length - 1].date])
|
||||||
|
.tickSize(0)
|
||||||
|
.tickFormat(d3.timeFormat("%b %Y"))
|
||||||
|
)
|
||||||
|
.select(".domain")
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
g.selectAll(".tick text")
|
||||||
|
.style("fill", tailwindColors.gray[500])
|
||||||
|
.style("font-size", "12px")
|
||||||
|
.style("font-weight", "500")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("dx", (d, i) => {
|
||||||
|
// We know we only have 2 values
|
||||||
|
return i === 0 ? "5em" : "-5em";
|
||||||
|
})
|
||||||
|
.attr("dy", "0em");
|
||||||
|
|
||||||
|
// Line
|
||||||
|
const line = d3
|
||||||
|
.line()
|
||||||
|
.x((d) => x(d.date))
|
||||||
|
.y((d) => y(d.value));
|
||||||
|
|
||||||
|
g.append("path")
|
||||||
|
.datum(data)
|
||||||
|
.attr("fill", "none")
|
||||||
|
.attr("stroke", tailwindColors.green[500])
|
||||||
|
.attr("stroke-linejoin", "round")
|
||||||
|
.attr("stroke-linecap", "round")
|
||||||
|
.attr("stroke-width", 1.5)
|
||||||
|
.attr("class", "line-chart-path")
|
||||||
|
.attr("d", line);
|
||||||
|
|
||||||
|
const tooltip = d3
|
||||||
|
.select("#lineChart")
|
||||||
|
.append("div")
|
||||||
|
.style("position", "absolute")
|
||||||
|
.style("padding", "8px")
|
||||||
|
.style("font", "14px Inter, sans-serif")
|
||||||
|
.style("background", tailwindColors.white)
|
||||||
|
.style("border", `1px solid ${tailwindColors["alpha-black"][100]}`)
|
||||||
|
.style("border-radius", "10px")
|
||||||
|
.style("pointer-events", "none")
|
||||||
|
.style("opacity", 0); // Starts as hidden
|
||||||
|
|
||||||
|
// Helper to find the closest data point to the mouse
|
||||||
|
const bisectDate = d3.bisector(function (d) {
|
||||||
|
return d.date;
|
||||||
|
}).left;
|
||||||
|
|
||||||
|
// Create an invisible rectangle that captures mouse events (regular SVG elements don't capture mouse events by default)
|
||||||
|
g.append("rect")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.attr("fill", "none")
|
||||||
|
.attr("pointer-events", "all")
|
||||||
|
// When user hovers over the chart, show the tooltip and a circle at the closest data point
|
||||||
|
.on("mousemove", (event) => {
|
||||||
|
tooltip.style("opacity", 1);
|
||||||
|
|
||||||
|
const [xPos] = d3.pointer(event);
|
||||||
|
|
||||||
|
const x0 = bisectDate(data, x.invert(xPos));
|
||||||
|
const d0 = data[x0 - 1];
|
||||||
|
const d1 = data[x0];
|
||||||
|
const d = xPos - x(d0.date) > x(d1.date) - xPos ? d1 : d0;
|
||||||
|
|
||||||
|
g.selectAll(".data-point-circle").remove(); // Remove existing circles to ensure only one is shown at a time
|
||||||
|
g.append("circle")
|
||||||
|
.attr("class", "data-point-circle")
|
||||||
|
.attr("cx", x(d.date))
|
||||||
|
.attr("cy", y(d.value))
|
||||||
|
.attr("r", 8)
|
||||||
|
.attr("fill", tailwindColors.green[500])
|
||||||
|
.attr("fill-opacity", "0.1");
|
||||||
|
|
||||||
|
g.append("circle")
|
||||||
|
.attr("class", "data-point-circle")
|
||||||
|
.attr("cx", x(d.date))
|
||||||
|
.attr("cy", y(d.value))
|
||||||
|
.attr("r", 3)
|
||||||
|
.attr("fill", tailwindColors.green[500]);
|
||||||
|
|
||||||
|
tooltip
|
||||||
|
.html(
|
||||||
|
`<div style="margin-bottom: 4px; color: ${
|
||||||
|
tailwindColors.gray[500]
|
||||||
|
}">${d3.timeFormat("%b %Y")(d.date)}</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<svg width="10" height="10">
|
||||||
|
<circle cx="5" cy="5" r="4" stroke="${
|
||||||
|
d.change.direction === "up"
|
||||||
|
? tailwindColors.success
|
||||||
|
: d.change.direction === "down"
|
||||||
|
? tailwindColors.error
|
||||||
|
: tailwindColors.gray[500]
|
||||||
|
}" fill="transparent" stroke-width="1"></circle>
|
||||||
|
</svg>
|
||||||
|
${d.formatted} <span style="color: ${
|
||||||
|
d.change.direction === "up"
|
||||||
|
? tailwindColors.success
|
||||||
|
: d.change.direction === "down"
|
||||||
|
? tailwindColors.error
|
||||||
|
: tailwindColors.gray[500]
|
||||||
|
};"><span>${
|
||||||
|
d.change.direction === "up"
|
||||||
|
? "+"
|
||||||
|
: d.change.direction === "down"
|
||||||
|
? "-"
|
||||||
|
: ""
|
||||||
|
}</span>${d.change.value} (${d.change.percentage})</span>
|
||||||
|
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
.style("left", event.pageX + 10 + "px")
|
||||||
|
.style("top", event.pageY - 10 + "px");
|
||||||
|
|
||||||
|
g.selectAll(".guideline").remove(); // Remove existing line to ensure only one is shown at a time
|
||||||
|
g.append("line")
|
||||||
|
.attr("class", "guideline")
|
||||||
|
.attr("x1", x(d.date))
|
||||||
|
.attr("y1", 0)
|
||||||
|
.attr("x2", x(d.date))
|
||||||
|
.attr("y2", height)
|
||||||
|
.attr("stroke", tailwindColors.gray[300])
|
||||||
|
.attr("stroke-dasharray", "4, 4");
|
||||||
|
})
|
||||||
|
.on("mouseout", () => {
|
||||||
|
g.selectAll(".guideline").remove();
|
||||||
|
g.selectAll(".data-point-circle").remove();
|
||||||
|
tooltip.style("opacity", 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
141
app/javascript/tailwindColors.js
Normal file
141
app/javascript/tailwindColors.js
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
/**
|
||||||
|
* To keep consistent styling across the app, this file can be imported in
|
||||||
|
* Stimulus controllers to reference our color palette. Mostly used for D3 charts.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
transparent: "transparent",
|
||||||
|
current: "currentColor",
|
||||||
|
white: "#ffffff",
|
||||||
|
black: "#0B0B0B",
|
||||||
|
success: "#10A861",
|
||||||
|
warning: "#F79009",
|
||||||
|
error: "#F13636",
|
||||||
|
gray: {
|
||||||
|
25: "#FAFAFA",
|
||||||
|
50: "#F5F5F5",
|
||||||
|
100: "#F0F0F0",
|
||||||
|
200: "#E5E5E5",
|
||||||
|
300: "#D6D6D6",
|
||||||
|
400: "#A3A3A3",
|
||||||
|
500: "#737373",
|
||||||
|
600: "#525252",
|
||||||
|
700: "#3D3D3D",
|
||||||
|
800: "#212121",
|
||||||
|
900: "#141414",
|
||||||
|
},
|
||||||
|
"alpha-white": {
|
||||||
|
25: "rgba(255, 255, 255, 0.03)",
|
||||||
|
50: "rgba(255, 255, 255, 0.05)",
|
||||||
|
100: "rgba(255, 255, 255, 0.08)",
|
||||||
|
200: "rgba(255, 255, 255, 0.1)",
|
||||||
|
300: "rgba(255, 255, 255, 0.15)",
|
||||||
|
400: "rgba(255, 255, 255, 0.2)",
|
||||||
|
500: "rgba(255, 255, 255, 0.3)",
|
||||||
|
600: "rgba(255, 255, 255, 0.4)",
|
||||||
|
700: "rgba(255, 255, 255, 0.5)",
|
||||||
|
800: "rgba(255, 255, 255, 0.6)",
|
||||||
|
900: "rgba(255, 255, 255, 0.7)",
|
||||||
|
},
|
||||||
|
"alpha-black": {
|
||||||
|
25: "rgba(20, 20, 20, 0.03)",
|
||||||
|
50: "rgba(20, 20, 20, 0.05)",
|
||||||
|
100: "rgba(20, 20, 20, 0.08)",
|
||||||
|
200: "rgba(20, 20, 20, 0.1)",
|
||||||
|
300: "rgba(20, 20, 20, 0.15)",
|
||||||
|
400: "rgba(20, 20, 20, 0.2)",
|
||||||
|
500: "rgba(20, 20, 20, 0.3)",
|
||||||
|
600: "rgba(20, 20, 20, 0.4)",
|
||||||
|
700: "rgba(20, 20, 20, 0.5)",
|
||||||
|
800: "rgba(20, 20, 20, 0.6)",
|
||||||
|
900: "rgba(20, 20, 20, 0.7)",
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
25: "#FFFBFB",
|
||||||
|
50: "#FFF1F0",
|
||||||
|
100: "#FFDEDB",
|
||||||
|
200: "#FEB9B3",
|
||||||
|
300: "#F88C86",
|
||||||
|
400: "#ED4E4E",
|
||||||
|
500: "#F13636",
|
||||||
|
600: "#EC2222",
|
||||||
|
700: "#C91313",
|
||||||
|
800: "#A40E0E",
|
||||||
|
900: "#7E0707",
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
25: "#F6FEF9",
|
||||||
|
50: "#ECFDF3",
|
||||||
|
100: "#D1FADF",
|
||||||
|
200: "#A6F4C5",
|
||||||
|
300: "#6CE9A6",
|
||||||
|
400: "#32D583",
|
||||||
|
500: "#12B76A",
|
||||||
|
600: "#10A861",
|
||||||
|
700: "#078C52",
|
||||||
|
800: "#05603A",
|
||||||
|
900: "#054F31",
|
||||||
|
},
|
||||||
|
yellow: {
|
||||||
|
25: "#FFFCF5",
|
||||||
|
50: "#FFFAEB",
|
||||||
|
100: "#FEF0C7",
|
||||||
|
200: "#FEDF89",
|
||||||
|
300: "#FEC84B",
|
||||||
|
400: "#FDB022",
|
||||||
|
500: "#F79009",
|
||||||
|
600: "#DC6803",
|
||||||
|
700: "#B54708",
|
||||||
|
800: "#93370D",
|
||||||
|
900: "#7A2E0E",
|
||||||
|
},
|
||||||
|
cyan: {
|
||||||
|
25: "#F5FEFF",
|
||||||
|
50: "#ECFDFF",
|
||||||
|
100: "#CFF9FE",
|
||||||
|
200: "#A5F0FC",
|
||||||
|
300: "#67E3F9",
|
||||||
|
400: "#22CCEE",
|
||||||
|
500: "#06AED4",
|
||||||
|
600: "#088AB2",
|
||||||
|
700: "#0E7090",
|
||||||
|
800: "#155B75",
|
||||||
|
900: "#155B75",
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
25: "#F5FAFF",
|
||||||
|
50: "#EFF8FF",
|
||||||
|
100: "#D1E9FF",
|
||||||
|
200: "#B2DDFF",
|
||||||
|
300: "#84CAFF",
|
||||||
|
400: "#53B1FD",
|
||||||
|
500: "#2E90FA",
|
||||||
|
600: "#1570EF",
|
||||||
|
700: "#175CD3",
|
||||||
|
800: "#1849A9",
|
||||||
|
900: "#194185",
|
||||||
|
},
|
||||||
|
indigo: {
|
||||||
|
25: "#F5F8FF",
|
||||||
|
50: "#EFF4FF",
|
||||||
|
100: "#E0EAFF",
|
||||||
|
200: "#C7D7FE",
|
||||||
|
300: "#A4BCFD",
|
||||||
|
400: "#8098F9",
|
||||||
|
500: "#6172F3",
|
||||||
|
600: "#444CE7",
|
||||||
|
700: "#3538CD",
|
||||||
|
800: "#2D31A6",
|
||||||
|
900: "#2D3282",
|
||||||
|
},
|
||||||
|
violet: {
|
||||||
|
25: "#FBFAFF",
|
||||||
|
50: "#F5F3FF",
|
||||||
|
100: "#ECE9FE",
|
||||||
|
200: "#DDD6FE",
|
||||||
|
300: "#C3B5FD",
|
||||||
|
400: "#A48AFB",
|
||||||
|
500: "#875BF7",
|
||||||
|
600: "#7839EE",
|
||||||
|
700: "#6927DA",
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,5 +1,6 @@
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
belongs_to :family
|
belongs_to :family
|
||||||
|
has_many :account_balances
|
||||||
|
|
||||||
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
|
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
|
||||||
|
|
||||||
|
|
3
app/models/account_balance.rb
Normal file
3
app/models/account_balance.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
class AccountBalance < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
end
|
|
@ -1,2 +1,114 @@
|
||||||
<h2 class="text-2xl font-semibold font-display"><%= @account.name %></h2>
|
<div class="space-y-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="bg-green-600/10 rounded-full h-8 w-8 flex items-center justify-center">
|
||||||
|
<span class="text-green-600"><%= @account.name[0].upcase %></span>
|
||||||
|
</div>
|
||||||
|
<h2 class="font-medium text-xl"><%= @account.name %></h2>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="relative cursor-not-allowed">
|
||||||
|
<div class="flex items-center gap-2 px-3 py-2">
|
||||||
|
<span class="text-gray-900">USD $</span>
|
||||||
|
<%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cursor-not-allowed">
|
||||||
|
<%= lucide_icon("more-horizontal", class: "w-5 h-5 text-gray-500") %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow-xs rounded-xl border border-alpha-black-25 rounded-lg">
|
||||||
|
<div class="p-4 flex justify-between">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p class="text-sm text-gray-500">Total Value</p>
|
||||||
|
<%# TODO: Will need a normalized way to split a formatted monetary value into these 3 parts %>
|
||||||
|
<p class="text-gray-900">
|
||||||
|
<span class="text-gray-500"><%= number_to_currency(@account.converted_balance)[0] %></span>
|
||||||
|
<span class="text-xl font-medium"><%= number_with_delimiter(@account.converted_balance.round) %></span>
|
||||||
|
<span class="text-gray-500">.<%= number_to_currency(@account.converted_balance, precision: 2)[-2, 2] %></span>
|
||||||
|
</p>
|
||||||
|
<% if @account.dollar_change == 0 %>
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
<span class="text-gray-500">No change vs last month</span>
|
||||||
|
</p>
|
||||||
|
<% else %>
|
||||||
|
<p class="text-sm <%= @account.dollar_change > 0 ? 'text-green-600' : 'text-red-600' %>">
|
||||||
|
<span><%= @account.dollar_change > 0 ? '+' : '-' %><%= number_to_currency(@account.dollar_change.abs, precision: 2) %></span>
|
||||||
|
<span>
|
||||||
|
(<%= lucide_icon(@account.dollar_change > 0 ? 'arrow-up' : 'arrow-down', class: "w-4 h-4 align-text-bottom inline") %>
|
||||||
|
<span><%= @account.percent_change %>%</span>)
|
||||||
|
</span>
|
||||||
|
<span class="text-gray-500">vs last month</span>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="flex items-center text-sm gap-2 p-2 border border-alpha-black-100 shadow-xs rounded-lg cursor-not-allowed">
|
||||||
|
<span class="text-gray-900 pl-1">1M</span>
|
||||||
|
<%= lucide_icon("chevron-down", class: "w-5 h-5 text-gray-500") %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-96 flex items-center justify-center text-2xl font-bold">
|
||||||
|
<div data-controller="line-chart" id="lineChart" class="w-full h-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h3 class="font-medium text-xl">History</h3>
|
||||||
|
<button class="cursor-not-allowed flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg">
|
||||||
|
<%= lucide_icon("plus", class: "w-5 h-5 text-gray-900") %>
|
||||||
|
<span>New entry</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-gray-25 p-1">
|
||||||
|
<div class="flex flex-col rounded-lg space-y-1">
|
||||||
|
<div class="flex justify-between gap-10 text-xs font-medium text-gray-500 uppercase py-2">
|
||||||
|
<div class="ml-4 flex-1">
|
||||||
|
DATE
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-right">VALUE</div>
|
||||||
|
<div class="flex-1 text-right">CHANGE</div>
|
||||||
|
<div class="w-12"></div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-lg py-2 bg-white border-alpha-black-25 shadow-xs">
|
||||||
|
<% @account.balances.each_with_index do |balance, index| %>
|
||||||
|
<div>
|
||||||
|
<div class="p-4 flex items-center justify-between gap-10 w-full">
|
||||||
|
<div class="flex-1 flex items-center gap-4">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-gray-500/5 p-1.5 flex items-center justify-center">
|
||||||
|
<%= lucide_icon(balance.icon, class: "w-4 h-4 #{balance.change > 0 ? 'text-green-500' : balance.change < 0 ? 'text-red-500' : 'text-gray-500'}") %>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
<p class=""><%= balance.date %></p>
|
||||||
|
<p class="text-gray-500"><%= balance.description %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-sm font-medium text-right"><%= number_to_currency(balance.amount, precision: 2) %></div>
|
||||||
|
<div class="flex-1 text-right text-sm font-medium">
|
||||||
|
<% if balance.change == 0 %>
|
||||||
|
<span class="text-gray-500">No change</span>
|
||||||
|
<% else %>
|
||||||
|
<span class="<%= balance.change > 0 ? 'text-green-500' : 'text-red-500' %>"><%= balance.change > 0 ? '+' : '' %>$<%= balance.change.abs %></span>
|
||||||
|
<span class="<%= balance.change > 0 ? 'text-green-500' : 'text-red-500' %>">
|
||||||
|
(<%= lucide_icon(balance.icon, class: "w-4 h-4 align-text-bottom inline") %> <%= balance.percentage_change %>%)</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="w-8 cursor-not-allowed">
|
||||||
|
<%= lucide_icon("more-horizontal", class: "w-5 h-5 text-gray-500") %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% if index < @account.balances.size - 1 %>
|
||||||
|
<div class="h-px bg-alpha-black-50 mr-6 ml-16"></div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<button class="cursor-not-allowed hover:bg-white w-full flex items-center justify-center gap-2 text-gray-500 px-4 py-2 rounded-md">
|
||||||
|
<%= lucide_icon("plus", class: "w-4 h-4") %> New entry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<main class="flex-grow py-5 pr-5">
|
<main class="flex-grow py-6 px-20">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,3 +6,42 @@ pin "@hotwired/stimulus", to: "stimulus.min.js"
|
||||||
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
||||||
pin_all_from "app/javascript/controllers", under: "controllers"
|
pin_all_from "app/javascript/controllers", under: "controllers"
|
||||||
pin "@github/hotkey", to: "@github--hotkey.js" # @3.1.0
|
pin "@github/hotkey", to: "@github--hotkey.js" # @3.1.0
|
||||||
|
|
||||||
|
# Custom namespace for local files
|
||||||
|
pin "@maybe/tailwindcolors", to: "tailwindColors.js"
|
||||||
|
|
||||||
|
# D3 packages
|
||||||
|
pin "d3" # @7.8.5
|
||||||
|
pin "d3-array" # @3.2.4
|
||||||
|
pin "d3-axis" # @3.0.0
|
||||||
|
pin "d3-brush" # @3.0.0
|
||||||
|
pin "d3-chord" # @3.0.1
|
||||||
|
pin "d3-color" # @3.1.0
|
||||||
|
pin "d3-contour" # @4.0.2
|
||||||
|
pin "d3-delaunay" # @6.0.4
|
||||||
|
pin "d3-dispatch" # @3.0.1
|
||||||
|
pin "d3-drag" # @3.0.0
|
||||||
|
pin "d3-dsv" # @3.0.1
|
||||||
|
pin "d3-ease" # @3.0.1
|
||||||
|
pin "d3-fetch" # @3.0.1
|
||||||
|
pin "d3-force" # @3.0.0
|
||||||
|
pin "d3-format" # @3.1.0
|
||||||
|
pin "d3-geo" # @3.1.0
|
||||||
|
pin "d3-hierarchy" # @3.1.2
|
||||||
|
pin "d3-interpolate" # @3.0.1
|
||||||
|
pin "d3-path" # @3.1.0
|
||||||
|
pin "d3-polygon" # @3.0.1
|
||||||
|
pin "d3-quadtree" # @3.0.1
|
||||||
|
pin "d3-random" # @3.0.1
|
||||||
|
pin "d3-scale" # @4.0.2
|
||||||
|
pin "d3-scale-chromatic" # @3.0.0
|
||||||
|
pin "d3-selection" # @3.0.0
|
||||||
|
pin "d3-shape" # @3.2.0
|
||||||
|
pin "d3-time" # @3.1.0
|
||||||
|
pin "d3-time-format" # @4.1.0
|
||||||
|
pin "d3-timer" # @3.0.1
|
||||||
|
pin "d3-transition" # @3.0.1
|
||||||
|
pin "d3-zoom" # @3.0.0
|
||||||
|
pin "delaunator" # @5.0.1
|
||||||
|
pin "internmap" # @2.0.3
|
||||||
|
pin "robust-predicates" # @3.0.2
|
||||||
|
|
|
@ -197,6 +197,18 @@ module.exports = {
|
||||||
"2xl": "0px 24px 48px -12px rgba(11, 11, 11, 0.12)",
|
"2xl": "0px 24px 48px -12px rgba(11, 11, 11, 0.12)",
|
||||||
"3xl": "0px 32px 64px -12px rgba(11, 11, 11, 0.14)",
|
"3xl": "0px 32px 64px -12px rgba(11, 11, 11, 0.14)",
|
||||||
},
|
},
|
||||||
|
borderRadius: {
|
||||||
|
none: "0",
|
||||||
|
full: "9999px",
|
||||||
|
xs: "2px",
|
||||||
|
sm: "4px",
|
||||||
|
md: "8px",
|
||||||
|
DEFAULT: "8px",
|
||||||
|
lg: "10px",
|
||||||
|
xl: "12px",
|
||||||
|
"2xl": "16px",
|
||||||
|
"3xl": "24px",
|
||||||
|
},
|
||||||
extend: {
|
extend: {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
display: ["Inter var", ...defaultTheme.fontFamily.sans],
|
display: ["Inter var", ...defaultTheme.fontFamily.sans],
|
||||||
|
@ -205,14 +217,14 @@ module.exports = {
|
||||||
"2xs": ".625rem",
|
"2xs": ".625rem",
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
'appear-then-fade': {
|
"appear-then-fade": {
|
||||||
'0%,100%': { opacity: 0 },
|
"0%,100%": { opacity: 0 },
|
||||||
'5%,90%': { opacity: 1 },
|
"5%,90%": { opacity: 1 },
|
||||||
},
|
},
|
||||||
'stroke-fill': {
|
"stroke-fill": {
|
||||||
to: { 'stroke-dashoffset': 0 },
|
to: { "stroke-dashoffset": 0 },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
14
db/migrate/20240212150110_create_account_balances.rb
Normal file
14
db/migrate/20240212150110_create_account_balances.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
class CreateAccountBalances < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
create_table :account_balances, id: :uuid do |t|
|
||||||
|
t.references :account, null: false, type: :uuid, foreign_key: { on_delete: :cascade }
|
||||||
|
t.date :date, null: false
|
||||||
|
t.decimal :balance, precision: 19, scale: 4, null: false
|
||||||
|
t.string :currency, default: "USD", null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :account_balances, [ :account_id, :date ], unique: true
|
||||||
|
end
|
||||||
|
end
|
14
db/schema.rb
generated
14
db/schema.rb
generated
|
@ -10,11 +10,22 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2024_02_10_155058) do
|
ActiveRecord::Schema[7.2].define(version: 2024_02_12_150110) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pgcrypto"
|
enable_extension "pgcrypto"
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
create_table "account_balances", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
t.uuid "account_id", null: false
|
||||||
|
t.date "date", null: false
|
||||||
|
t.decimal "balance", precision: 19, scale: 4, null: false
|
||||||
|
t.string "currency", default: "USD", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id", "date"], name: "index_account_balances_on_account_id_and_date", unique: true
|
||||||
|
t.index ["account_id"], name: "index_account_balances_on_account_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "account_credits", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "account_credits", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
@ -195,6 +206,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_02_10_155058) do
|
||||||
t.index ["family_id"], name: "index_users_on_family_id"
|
t.index ["family_id"], name: "index_users_on_family_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_foreign_key "account_balances", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "accounts", "families"
|
add_foreign_key "accounts", "families"
|
||||||
add_foreign_key "users", "families"
|
add_foreign_key "users", "families"
|
||||||
end
|
end
|
||||||
|
|
13
test/fixtures/account_balances.yml
vendored
Normal file
13
test/fixtures/account_balances.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||||
|
|
||||||
|
one:
|
||||||
|
account: dylan_checking
|
||||||
|
date: 2024-02-12
|
||||||
|
balance: 9.99
|
||||||
|
currency: MyString
|
||||||
|
|
||||||
|
two:
|
||||||
|
account: richards_savings
|
||||||
|
date: 2024-02-12
|
||||||
|
balance: 9.99
|
||||||
|
currency: MyString
|
7
test/models/account_balance_test.rb
Normal file
7
test/models/account_balance_test.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class AccountBalanceTest < ActiveSupport::TestCase
|
||||||
|
# test "the truth" do
|
||||||
|
# assert true
|
||||||
|
# end
|
||||||
|
end
|
2
vendor/javascript/d3-array.js
vendored
Normal file
2
vendor/javascript/d3-array.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-axis.js
vendored
Normal file
2
vendor/javascript/d3-axis.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
function identity(t){return t}var t=1,n=2,r=3,i=4,e=1e-6;function translateX(t){return"translate("+t+",0)"}function translateY(t){return"translate(0,"+t+")"}function number(t){return n=>+t(n)}function center(t,n){n=Math.max(0,t.bandwidth()-2*n)/2;t.round()&&(n=Math.round(n));return r=>+t(r)+n}function entering(){return!this.__axis}function axis(a,s){var o=[],u=null,c=null,l=6,x=6,f=3,d="undefined"!==typeof window&&window.devicePixelRatio>1?0:.5,m=a===t||a===i?-1:1,h=a===i||a===n?"x":"y",g=a===t||a===r?translateX:translateY;function axis(p){var k=null==u?s.ticks?s.ticks.apply(s,o):s.domain():u,y=null==c?s.tickFormat?s.tickFormat.apply(s,o):identity:c,A=Math.max(l,0)+f,M=s.range(),v=+M[0]+d,w=+M[M.length-1]+d,_=(s.bandwidth?center:number)(s.copy(),d),b=p.selection?p.selection():p,F=b.selectAll(".domain").data([null]),V=b.selectAll(".tick").data(k,s).order(),z=V.exit(),H=V.enter().append("g").attr("class","tick"),C=V.select("line"),R=V.select("text");F=F.merge(F.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor"));V=V.merge(H);C=C.merge(H.append("line").attr("stroke","currentColor").attr(h+"2",m*l));R=R.merge(H.append("text").attr("fill","currentColor").attr(h,m*A).attr("dy",a===t?"0em":a===r?"0.71em":"0.32em"));if(p!==b){F=F.transition(p);V=V.transition(p);C=C.transition(p);R=R.transition(p);z=z.transition(p).attr("opacity",e).attr("transform",(function(t){return isFinite(t=_(t))?g(t+d):this.getAttribute("transform")}));H.attr("opacity",e).attr("transform",(function(t){var n=this.parentNode.__axis;return g((n&&isFinite(n=n(t))?n:_(t))+d)}))}z.remove();F.attr("d",a===i||a===n?x?"M"+m*x+","+v+"H"+d+"V"+w+"H"+m*x:"M"+d+","+v+"V"+w:x?"M"+v+","+m*x+"V"+d+"H"+w+"V"+m*x:"M"+v+","+d+"H"+w);V.attr("opacity",1).attr("transform",(function(t){return g(_(t)+d)}));C.attr(h+"2",m*l);R.attr(h,m*A).text(y);b.filter(entering).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",a===n?"start":a===i?"end":"middle");b.each((function(){this.__axis=_}))}axis.scale=function(t){return arguments.length?(s=t,axis):s};axis.ticks=function(){return o=Array.from(arguments),axis};axis.tickArguments=function(t){return arguments.length?(o=null==t?[]:Array.from(t),axis):o.slice()};axis.tickValues=function(t){return arguments.length?(u=null==t?null:Array.from(t),axis):u&&u.slice()};axis.tickFormat=function(t){return arguments.length?(c=t,axis):c};axis.tickSize=function(t){return arguments.length?(l=x=+t,axis):l};axis.tickSizeInner=function(t){return arguments.length?(l=+t,axis):l};axis.tickSizeOuter=function(t){return arguments.length?(x=+t,axis):x};axis.tickPadding=function(t){return arguments.length?(f=+t,axis):f};axis.offset=function(t){return arguments.length?(d=+t,axis):d};return axis}function axisTop(n){return axis(t,n)}function axisRight(t){return axis(n,t)}function axisBottom(t){return axis(r,t)}function axisLeft(t){return axis(i,t)}export{axisBottom,axisLeft,axisRight,axisTop};
|
||||||
|
|
2
vendor/javascript/d3-brush.js
vendored
Normal file
2
vendor/javascript/d3-brush.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-chord.js
vendored
Normal file
2
vendor/javascript/d3-chord.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import{path as n}from"d3-path";var r=Math.abs;var t=Math.cos;var e=Math.sin;var o=Math.PI;var u=o/2;var a=2*o;var l=Math.max;var i=1e-12;function range(n,r){return Array.from({length:r-n},((r,t)=>n+t))}function compareValue(n){return function(r,t){return n(r.source.value+r.target.value,t.source.value+t.target.value)}}function chord(){return chord$1(false,false)}function chordTranspose(){return chord$1(false,true)}function chordDirected(){return chord$1(true,false)}function chord$1(n,r){var t=0,e=null,o=null,u=null;function chord(i){var c,s=i.length,f=new Array(s),d=range(0,s),g=new Array(s*s),b=new Array(s),h=0;i=Float64Array.from({length:s*s},r?(n,r)=>i[r%s][r/s|0]:(n,r)=>i[r/s|0][r%s]);for(let r=0;r<s;++r){let t=0;for(let e=0;e<s;++e)t+=i[r*s+e]+n*i[e*s+r];h+=f[r]=t}h=l(0,a-t*s)/h;c=h?t:a/s;{let r=0;e&&d.sort(((n,r)=>e(f[n],f[r])));for(const t of d){const e=r;if(n){const n=range(1+~s,s).filter((n=>n<0?i[~n*s+t]:i[t*s+n]));o&&n.sort(((n,r)=>o(n<0?-i[~n*s+t]:i[t*s+n],r<0?-i[~r*s+t]:i[t*s+r])));for(const e of n)if(e<0){const n=g[~e*s+t]||(g[~e*s+t]={source:null,target:null});n.target={index:t,startAngle:r,endAngle:r+=i[~e*s+t]*h,value:i[~e*s+t]}}else{const n=g[t*s+e]||(g[t*s+e]={source:null,target:null});n.source={index:t,startAngle:r,endAngle:r+=i[t*s+e]*h,value:i[t*s+e]}}b[t]={index:t,startAngle:e,endAngle:r,value:f[t]}}else{const n=range(0,s).filter((n=>i[t*s+n]||i[n*s+t]));o&&n.sort(((n,r)=>o(i[t*s+n],i[t*s+r])));for(const e of n){let n;if(t<e){n=g[t*s+e]||(g[t*s+e]={source:null,target:null});n.source={index:t,startAngle:r,endAngle:r+=i[t*s+e]*h,value:i[t*s+e]}}else{n=g[e*s+t]||(g[e*s+t]={source:null,target:null});n.target={index:t,startAngle:r,endAngle:r+=i[t*s+e]*h,value:i[t*s+e]};t===e&&(n.source=n.target)}if(n.source&&n.target&&n.source.value<n.target.value){const r=n.source;n.source=n.target;n.target=r}}b[t]={index:t,startAngle:e,endAngle:r,value:f[t]}}r+=c}}g=Object.values(g);g.groups=b;return u?g.sort(u):g}chord.padAngle=function(n){return arguments.length?(t=l(0,n),chord):t};chord.sortGroups=function(n){return arguments.length?(e=n,chord):e};chord.sortSubgroups=function(n){return arguments.length?(o=n,chord):o};chord.sortChords=function(n){return arguments.length?(null==n?u=null:(u=compareValue(n))._=n,chord):u&&u._};return chord}var c=Array.prototype.slice;function constant(n){return function(){return n}}function defaultSource(n){return n.source}function defaultTarget(n){return n.target}function defaultRadius(n){return n.radius}function defaultStartAngle(n){return n.startAngle}function defaultEndAngle(n){return n.endAngle}function defaultPadAngle(){return 0}function defaultArrowheadRadius(){return 10}function ribbon(o){var a=defaultSource,l=defaultTarget,s=defaultRadius,f=defaultRadius,d=defaultStartAngle,g=defaultEndAngle,b=defaultPadAngle,h=null;function ribbon(){var p,A=a.apply(this,arguments),v=l.apply(this,arguments),y=b.apply(this,arguments)/2,T=c.call(arguments),x=+s.apply(this,(T[0]=A,T)),m=d.apply(this,T)-u,R=g.apply(this,T)-u,w=+f.apply(this,(T[0]=v,T)),$=d.apply(this,T)-u,M=g.apply(this,T)-u;h||(h=p=n());if(y>i){r(R-m)>2*y+i?R>m?(m+=y,R-=y):(m-=y,R+=y):m=R=(m+R)/2;r(M-$)>2*y+i?M>$?($+=y,M-=y):($-=y,M+=y):$=M=($+M)/2}h.moveTo(x*t(m),x*e(m));h.arc(0,0,x,m,R);if(m!==$||R!==M)if(o){var S=+o.apply(this,arguments),C=w-S,P=($+M)/2;h.quadraticCurveTo(0,0,C*t($),C*e($));h.lineTo(w*t(P),w*e(P));h.lineTo(C*t(M),C*e(M))}else{h.quadraticCurveTo(0,0,w*t($),w*e($));h.arc(0,0,w,$,M)}h.quadraticCurveTo(0,0,x*t(m),x*e(m));h.closePath();if(p)return h=null,p+""||null}o&&(ribbon.headRadius=function(n){return arguments.length?(o="function"===typeof n?n:constant(+n),ribbon):o});ribbon.radius=function(n){return arguments.length?(s=f="function"===typeof n?n:constant(+n),ribbon):s};ribbon.sourceRadius=function(n){return arguments.length?(s="function"===typeof n?n:constant(+n),ribbon):s};ribbon.targetRadius=function(n){return arguments.length?(f="function"===typeof n?n:constant(+n),ribbon):f};ribbon.startAngle=function(n){return arguments.length?(d="function"===typeof n?n:constant(+n),ribbon):d};ribbon.endAngle=function(n){return arguments.length?(g="function"===typeof n?n:constant(+n),ribbon):g};ribbon.padAngle=function(n){return arguments.length?(b="function"===typeof n?n:constant(+n),ribbon):b};ribbon.source=function(n){return arguments.length?(a=n,ribbon):a};ribbon.target=function(n){return arguments.length?(l=n,ribbon):l};ribbon.context=function(n){return arguments.length?(h=null==n?null:n,ribbon):h};return ribbon}function ribbon$1(){return ribbon()}function ribbonArrow(){return ribbon(defaultArrowheadRadius)}export{chord,chordDirected,chordTranspose,ribbon$1 as ribbon,ribbonArrow};
|
||||||
|
|
2
vendor/javascript/d3-color.js
vendored
Normal file
2
vendor/javascript/d3-color.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-contour.js
vendored
Normal file
2
vendor/javascript/d3-contour.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-delaunay.js
vendored
Normal file
2
vendor/javascript/d3-delaunay.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-dispatch.js
vendored
Normal file
2
vendor/javascript/d3-dispatch.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
var n={value:()=>{}};function dispatch(){for(var n,t=0,e=arguments.length,r={};t<e;++t){if(!(n=arguments[t]+"")||n in r||/[\s.]/.test(n))throw new Error("illegal type: "+n);r[n]=[]}return new Dispatch(r)}function Dispatch(n){this._=n}function parseTypenames(n,t){return n.trim().split(/^|\s+/).map((function(n){var e="",r=n.indexOf(".");r>=0&&(e=n.slice(r+1),n=n.slice(0,r));if(n&&!t.hasOwnProperty(n))throw new Error("unknown type: "+n);return{type:n,name:e}}))}Dispatch.prototype=dispatch.prototype={constructor:Dispatch,on:function(n,t){var e,r=this._,i=parseTypenames(n+"",r),a=-1,o=i.length;if(!(arguments.length<2)){if(null!=t&&"function"!==typeof t)throw new Error("invalid callback: "+t);while(++a<o)if(e=(n=i[a]).type)r[e]=set(r[e],n.name,t);else if(null==t)for(e in r)r[e]=set(r[e],n.name,null);return this}while(++a<o)if((e=(n=i[a]).type)&&(e=get(r[e],n.name)))return e},copy:function(){var n={},t=this._;for(var e in t)n[e]=t[e].slice();return new Dispatch(n)},call:function(n,t){if((e=arguments.length-2)>0)for(var e,r,i=new Array(e),a=0;a<e;++a)i[a]=arguments[a+2];if(!this._.hasOwnProperty(n))throw new Error("unknown type: "+n);for(r=this._[n],a=0,e=r.length;a<e;++a)r[a].value.apply(t,i)},apply:function(n,t,e){if(!this._.hasOwnProperty(n))throw new Error("unknown type: "+n);for(var r=this._[n],i=0,a=r.length;i<a;++i)r[i].value.apply(t,e)}};function get(n,t){for(var e,r=0,i=n.length;r<i;++r)if((e=n[r]).name===t)return e.value}function set(t,e,r){for(var i=0,a=t.length;i<a;++i)if(t[i].name===e){t[i]=n,t=t.slice(0,i).concat(t.slice(i+1));break}null!=r&&t.push({name:e,value:r});return t}export{dispatch};
|
||||||
|
|
2
vendor/javascript/d3-drag.js
vendored
Normal file
2
vendor/javascript/d3-drag.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import{dispatch as e}from"d3-dispatch";import{select as t,pointer as n}from"d3-selection";const r={passive:false};const a={capture:true,passive:false};function nopropagation(e){e.stopImmediatePropagation()}function noevent(e){e.preventDefault();e.stopImmediatePropagation()}function nodrag(e){var n=e.document.documentElement,r=t(e).on("dragstart.drag",noevent,a);if("onselectstart"in n)r.on("selectstart.drag",noevent,a);else{n.__noselect=n.style.MozUserSelect;n.style.MozUserSelect="none"}}function yesdrag(e,n){var r=e.document.documentElement,o=t(e).on("dragstart.drag",null);if(n){o.on("click.drag",noevent,a);setTimeout((function(){o.on("click.drag",null)}),0)}if("onselectstart"in r)o.on("selectstart.drag",null);else{r.style.MozUserSelect=r.__noselect;delete r.__noselect}}var constant=e=>()=>e;function DragEvent(e,{sourceEvent:t,subject:n,target:r,identifier:a,active:o,x:u,y:i,dx:c,dy:l,dispatch:d}){Object.defineProperties(this,{type:{value:e,enumerable:true,configurable:true},sourceEvent:{value:t,enumerable:true,configurable:true},subject:{value:n,enumerable:true,configurable:true},target:{value:r,enumerable:true,configurable:true},identifier:{value:a,enumerable:true,configurable:true},active:{value:o,enumerable:true,configurable:true},x:{value:u,enumerable:true,configurable:true},y:{value:i,enumerable:true,configurable:true},dx:{value:c,enumerable:true,configurable:true},dy:{value:l,enumerable:true,configurable:true},_:{value:d}})}DragEvent.prototype.on=function(){var e=this._.on.apply(this._,arguments);return e===this._?this:e};function defaultFilter(e){return!e.ctrlKey&&!e.button}function defaultContainer(){return this.parentNode}function defaultSubject(e,t){return null==t?{x:e.x,y:e.y}:t}function defaultTouchable(){return navigator.maxTouchPoints||"ontouchstart"in this}function drag(){var o,u,i,c,l=defaultFilter,d=defaultContainer,s=defaultSubject,f=defaultTouchable,g={},v=e("start","drag","end"),h=0,m=0;function drag(e){e.on("mousedown.drag",mousedowned).filter(f).on("touchstart.drag",touchstarted).on("touchmove.drag",touchmoved,r).on("touchend.drag touchcancel.drag",touchended).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function mousedowned(e,n){if(!c&&l.call(this,e,n)){var r=beforestart(this,d.call(this,e,n),e,n,"mouse");if(r){t(e.view).on("mousemove.drag",mousemoved,a).on("mouseup.drag",mouseupped,a);nodrag(e.view);nopropagation(e);i=false;o=e.clientX;u=e.clientY;r("start",e)}}}function mousemoved(e){noevent(e);if(!i){var t=e.clientX-o,n=e.clientY-u;i=t*t+n*n>m}g.mouse("drag",e)}function mouseupped(e){t(e.view).on("mousemove.drag mouseup.drag",null);yesdrag(e.view,i);noevent(e);g.mouse("end",e)}function touchstarted(e,t){if(l.call(this,e,t)){var n,r,a=e.changedTouches,o=d.call(this,e,t),u=a.length;for(n=0;n<u;++n)if(r=beforestart(this,o,e,t,a[n].identifier,a[n])){nopropagation(e);r("start",e,a[n])}}}function touchmoved(e){var t,n,r=e.changedTouches,a=r.length;for(t=0;t<a;++t)if(n=g[r[t].identifier]){noevent(e);n("drag",e,r[t])}}function touchended(e){var t,n,r=e.changedTouches,a=r.length;c&&clearTimeout(c);c=setTimeout((function(){c=null}),500);for(t=0;t<a;++t)if(n=g[r[t].identifier]){nopropagation(e);n("end",e,r[t])}}function beforestart(e,t,r,a,o,u){var i,c,l,d=v.copy(),f=n(u||r,t);if(null!=(l=s.call(e,new DragEvent("beforestart",{sourceEvent:r,target:drag,identifier:o,active:h,x:f[0],y:f[1],dx:0,dy:0,dispatch:d}),a))){i=l.x-f[0]||0;c=l.y-f[1]||0;return function gesture(r,u,s){var v,m=f;switch(r){case"start":g[o]=gesture,v=h++;break;case"end":delete g[o],--h;case"drag":f=n(s||u,t),v=h;break}d.call(r,e,new DragEvent(r,{sourceEvent:u,subject:l,target:drag,identifier:o,active:v,x:f[0]+i,y:f[1]+c,dx:f[0]-m[0],dy:f[1]-m[1],dispatch:d}),a)}}}drag.filter=function(e){return arguments.length?(l="function"===typeof e?e:constant(!!e),drag):l};drag.container=function(e){return arguments.length?(d="function"===typeof e?e:constant(e),drag):d};drag.subject=function(e){return arguments.length?(s="function"===typeof e?e:constant(e),drag):s};drag.touchable=function(e){return arguments.length?(f="function"===typeof e?e:constant(!!e),drag):f};drag.on=function(){var e=v.on.apply(v,arguments);return e===v?drag:e};drag.clickDistance=function(e){return arguments.length?(m=(e=+e)*e,drag):Math.sqrt(m)};return drag}export{drag,nodrag as dragDisable,yesdrag as dragEnable};
|
||||||
|
|
2
vendor/javascript/d3-dsv.js
vendored
Normal file
2
vendor/javascript/d3-dsv.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
var r={},e={},t=34,a=10,o=13;function objectConverter(r){return new Function("d","return {"+r.map((function(r,e){return JSON.stringify(r)+": d["+e+'] || ""'})).join(",")+"}")}function customConverter(r,e){var t=objectConverter(r);return function(a,o){return e(t(a),o,r)}}function inferColumns(r){var e=Object.create(null),t=[];r.forEach((function(r){for(var a in r)a in e||t.push(e[a]=a)}));return t}function pad(r,e){var t=r+"",a=t.length;return a<e?new Array(e-a+1).join(0)+t:t}function formatYear(r){return r<0?"-"+pad(-r,6):r>9999?"+"+pad(r,6):pad(r,4)}function formatDate(r){var e=r.getUTCHours(),t=r.getUTCMinutes(),a=r.getUTCSeconds(),o=r.getUTCMilliseconds();return isNaN(r)?"Invalid Date":formatYear(r.getUTCFullYear(),4)+"-"+pad(r.getUTCMonth()+1,2)+"-"+pad(r.getUTCDate(),2)+(o?"T"+pad(e,2)+":"+pad(t,2)+":"+pad(a,2)+"."+pad(o,3)+"Z":a?"T"+pad(e,2)+":"+pad(t,2)+":"+pad(a,2)+"Z":t||e?"T"+pad(e,2)+":"+pad(t,2)+"Z":"")}function dsv(n){var u=new RegExp('["'+n+"\n\r]"),f=n.charCodeAt(0);function parse(r,e){var t,a,o=parseRows(r,(function(r,o){if(t)return t(r,o-1);a=r,t=e?customConverter(r,e):objectConverter(r)}));o.columns=a||[];return o}function parseRows(n,u){var i,s=[],c=n.length,l=0,d=0,m=c<=0,p=false;n.charCodeAt(c-1)===a&&--c;n.charCodeAt(c-1)===o&&--c;function token(){if(m)return e;if(p)return p=false,r;var u,i,s=l;if(n.charCodeAt(s)===t){while(l++<c&&n.charCodeAt(l)!==t||n.charCodeAt(++l)===t);if((u=l)>=c)m=true;else if((i=n.charCodeAt(l++))===a)p=true;else if(i===o){p=true;n.charCodeAt(l)===a&&++l}return n.slice(s+1,u-1).replace(/""/g,'"')}while(l<c){if((i=n.charCodeAt(u=l++))===a)p=true;else if(i===o){p=true;n.charCodeAt(l)===a&&++l}else if(i!==f)continue;return n.slice(s,u)}return m=true,n.slice(s,c)}while((i=token())!==e){var v=[];while(i!==r&&i!==e)v.push(i),i=token();u&&null==(v=u(v,d++))||s.push(v)}return s}function preformatBody(r,e){return r.map((function(r){return e.map((function(e){return formatValue(r[e])})).join(n)}))}function format(r,e){null==e&&(e=inferColumns(r));return[e.map(formatValue).join(n)].concat(preformatBody(r,e)).join("\n")}function formatBody(r,e){null==e&&(e=inferColumns(r));return preformatBody(r,e).join("\n")}function formatRows(r){return r.map(formatRow).join("\n")}function formatRow(r){return r.map(formatValue).join(n)}function formatValue(r){return null==r?"":r instanceof Date?formatDate(r):u.test(r+="")?'"'+r.replace(/"/g,'""')+'"':r}return{parse:parse,parseRows:parseRows,format:format,formatBody:formatBody,formatRows:formatRows,formatRow:formatRow,formatValue:formatValue}}var n=dsv(",");var u=n.parse;var f=n.parseRows;var i=n.format;var s=n.formatBody;var c=n.formatRows;var l=n.formatRow;var d=n.formatValue;var m=dsv("\t");var p=m.parse;var v=m.parseRows;var w=m.format;var C=m.formatBody;var h=m.formatRows;var R=m.formatRow;var g=m.formatValue;function autoType(r){for(var e in r){var t,a,o=r[e].trim();if(o)if("true"===o)o=true;else if("false"===o)o=false;else if("NaN"===o)o=NaN;else if(isNaN(t=+o)){if(!(a=o.match(/^([-+]\d{2})?\d{4}(-\d{2}(-\d{2})?)?(T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/)))continue;!T||!a[4]||a[7]||(o=o.replace(/-/g,"/").replace(/T/," "));o=new Date(o)}else o=t;else o=null;r[e]=o}return r}const T=new Date("2019-01-01T00:00").getHours()||new Date("2019-07-01T00:00").getHours();export{autoType,i as csvFormat,s as csvFormatBody,l as csvFormatRow,c as csvFormatRows,d as csvFormatValue,u as csvParse,f as csvParseRows,dsv as dsvFormat,w as tsvFormat,C as tsvFormatBody,R as tsvFormatRow,h as tsvFormatRows,g as tsvFormatValue,p as tsvParse,v as tsvParseRows};
|
||||||
|
|
2
vendor/javascript/d3-ease.js
vendored
Normal file
2
vendor/javascript/d3-ease.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
const linear=t=>+t;function quadIn(t){return t*t}function quadOut(t){return t*(2-t)}function quadInOut(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function cubicIn(t){return t*t*t}function cubicOut(t){return--t*t*t+1}function cubicInOut(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}var t=3;var n=function custom(t){t=+t;function polyIn(n){return Math.pow(n,t)}polyIn.exponent=custom;return polyIn}(t);var u=function custom(t){t=+t;function polyOut(n){return 1-Math.pow(1-n,t)}polyOut.exponent=custom;return polyOut}(t);var e=function custom(t){t=+t;function polyInOut(n){return((n*=2)<=1?Math.pow(n,t):2-Math.pow(2-n,t))/2}polyInOut.exponent=custom;return polyInOut}(t);var a=Math.PI,c=a/2;function sinIn(t){return 1===+t?1:1-Math.cos(t*c)}function sinOut(t){return Math.sin(t*c)}function sinInOut(t){return(1-Math.cos(a*t))/2}function tpmt(t){return 1.0009775171065494*(Math.pow(2,-10*t)-.0009765625)}function expIn(t){return tpmt(1-+t)}function expOut(t){return 1-tpmt(t)}function expInOut(t){return((t*=2)<=1?tpmt(1-t):2-tpmt(t-1))/2}function circleIn(t){return 1-Math.sqrt(1-t*t)}function circleOut(t){return Math.sqrt(1- --t*t)}function circleInOut(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}var s=4/11,r=6/11,o=8/11,i=3/4,O=9/11,I=10/11,p=15/16,f=21/22,l=63/64,m=1/s/s;function bounceIn(t){return 1-bounceOut(1-t)}function bounceOut(t){return(t=+t)<s?m*t*t:t<o?m*(t-=r)*t+i:t<I?m*(t-=O)*t+p:m*(t-=f)*t+l}function bounceInOut(t){return((t*=2)<=1?1-bounceOut(1-t):bounceOut(t-1)+1)/2}var b=1.70158;var h=function custom(t){t=+t;function backIn(n){return(n=+n)*n*(t*(n-1)+n)}backIn.overshoot=custom;return backIn}(b);var M=function custom(t){t=+t;function backOut(n){return--n*n*((n+1)*t+n)+1}backOut.overshoot=custom;return backOut}(b);var v=function custom(t){t=+t;function backInOut(n){return((n*=2)<1?n*n*((t+1)*n-t):(n-=2)*n*((t+1)*n+t)+2)/2}backInOut.overshoot=custom;return backInOut}(b);var x=2*Math.PI,d=1,k=.3;var y=function custom(t,n){var u=Math.asin(1/(t=Math.max(1,t)))*(n/=x);function elasticIn(e){return t*tpmt(- --e)*Math.sin((u-e)/n)}elasticIn.amplitude=function(t){return custom(t,n*x)};elasticIn.period=function(n){return custom(t,n)};return elasticIn}(d,k);var q=function custom(t,n){var u=Math.asin(1/(t=Math.max(1,t)))*(n/=x);function elasticOut(e){return 1-t*tpmt(e=+e)*Math.sin((e+u)/n)}elasticOut.amplitude=function(t){return custom(t,n*x)};elasticOut.period=function(n){return custom(t,n)};return elasticOut}(d,k);var B=function custom(t,n){var u=Math.asin(1/(t=Math.max(1,t)))*(n/=x);function elasticInOut(e){return((e=2*e-1)<0?t*tpmt(-e)*Math.sin((u-e)/n):2-t*tpmt(e)*Math.sin((u+e)/n))/2}elasticInOut.amplitude=function(t){return custom(t,n*x)};elasticInOut.period=function(n){return custom(t,n)};return elasticInOut}(d,k);export{v as easeBack,h as easeBackIn,v as easeBackInOut,M as easeBackOut,bounceOut as easeBounce,bounceIn as easeBounceIn,bounceInOut as easeBounceInOut,bounceOut as easeBounceOut,circleInOut as easeCircle,circleIn as easeCircleIn,circleInOut as easeCircleInOut,circleOut as easeCircleOut,cubicInOut as easeCubic,cubicIn as easeCubicIn,cubicInOut as easeCubicInOut,cubicOut as easeCubicOut,q as easeElastic,y as easeElasticIn,B as easeElasticInOut,q as easeElasticOut,expInOut as easeExp,expIn as easeExpIn,expInOut as easeExpInOut,expOut as easeExpOut,linear as easeLinear,e as easePoly,n as easePolyIn,e as easePolyInOut,u as easePolyOut,quadInOut as easeQuad,quadIn as easeQuadIn,quadInOut as easeQuadInOut,quadOut as easeQuadOut,sinInOut as easeSin,sinIn as easeSinIn,sinInOut as easeSinInOut,sinOut as easeSinOut};
|
||||||
|
|
2
vendor/javascript/d3-fetch.js
vendored
Normal file
2
vendor/javascript/d3-fetch.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import{dsvFormat as r,csvParse as t,tsvParse as e}from"d3-dsv";function responseBlob(r){if(!r.ok)throw new Error(r.status+" "+r.statusText);return r.blob()}function blob(r,t){return fetch(r,t).then(responseBlob)}function responseArrayBuffer(r){if(!r.ok)throw new Error(r.status+" "+r.statusText);return r.arrayBuffer()}function buffer(r,t){return fetch(r,t).then(responseArrayBuffer)}function responseText(r){if(!r.ok)throw new Error(r.status+" "+r.statusText);return r.text()}function text(r,t){return fetch(r,t).then(responseText)}function dsvParse(r){return function(t,e,n){2===arguments.length&&"function"===typeof e&&(n=e,e=void 0);return text(t,e).then((function(t){return r(t,n)}))}}function dsv(t,e,n,o){3===arguments.length&&"function"===typeof n&&(o=n,n=void 0);var s=r(t);return text(e,n).then((function(r){return s.parse(r,o)}))}var n=dsvParse(t);var o=dsvParse(e);function image(r,t){return new Promise((function(e,n){var o=new Image;for(var s in t)o[s]=t[s];o.onerror=n;o.onload=function(){e(o)};o.src=r}))}function responseJson(r){if(!r.ok)throw new Error(r.status+" "+r.statusText);if(204!==r.status&&205!==r.status)return r.json()}function json(r,t){return fetch(r,t).then(responseJson)}function parser(r){return(t,e)=>text(t,e).then((t=>(new DOMParser).parseFromString(t,r)))}var s=parser("application/xml");var u=parser("text/html");var f=parser("image/svg+xml");export{blob,buffer,n as csv,dsv,u as html,image,json,f as svg,text,o as tsv,s as xml};
|
||||||
|
|
2
vendor/javascript/d3-force.js
vendored
Normal file
2
vendor/javascript/d3-force.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-format.js
vendored
Normal file
2
vendor/javascript/d3-format.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-geo.js
vendored
Normal file
2
vendor/javascript/d3-geo.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-hierarchy.js
vendored
Normal file
2
vendor/javascript/d3-hierarchy.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-interpolate.js
vendored
Normal file
2
vendor/javascript/d3-interpolate.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-path.js
vendored
Normal file
2
vendor/javascript/d3-path.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
const t=Math.PI,h=2*t,i=1e-6,s=h-i;function append(t){this._+=t[0];for(let h=1,i=t.length;h<i;++h)this._+=arguments[h]+t[h]}function appendRound(t){let h=Math.floor(t);if(!(h>=0))throw new Error(`invalid digits: ${t}`);if(h>15)return append;const i=10**h;return function(t){this._+=t[0];for(let h=1,s=t.length;h<s;++h)this._+=Math.round(arguments[h]*i)/i+t[h]}}class Path{constructor(t){this._x0=this._y0=this._x1=this._y1=null;this._="";this._append=null==t?append:appendRound(t)}moveTo(t,h){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+h}`}closePath(){if(null!==this._x1){this._x1=this._x0,this._y1=this._y0;this._append`Z`}}lineTo(t,h){this._append`L${this._x1=+t},${this._y1=+h}`}quadraticCurveTo(t,h,i,s){this._append`Q${+t},${+h},${this._x1=+i},${this._y1=+s}`}bezierCurveTo(t,h,i,s,n,a){this._append`C${+t},${+h},${+i},${+s},${this._x1=+n},${this._y1=+a}`}arcTo(h,s,n,a,e){h=+h,s=+s,n=+n,a=+a,e=+e;if(e<0)throw new Error(`negative radius: ${e}`);let _=this._x1,$=this._y1,p=n-h,r=a-s,o=_-h,d=$-s,l=o*o+d*d;if(null===this._x1)this._append`M${this._x1=h},${this._y1=s}`;else if(l>i)if(Math.abs(d*p-r*o)>i&&e){let u=n-_,x=a-$,y=p*p+r*r,M=u*u+x*x,c=Math.sqrt(y),f=Math.sqrt(l),w=e*Math.tan((t-Math.acos((y+l-M)/(2*c*f)))/2),v=w/f,P=w/c;Math.abs(v-1)>i&&this._append`L${h+v*o},${s+v*d}`;this._append`A${e},${e},0,0,${+(d*u>o*x)},${this._x1=h+P*p},${this._y1=s+P*r}`}else this._append`L${this._x1=h},${this._y1=s}`;else;}arc(n,a,e,_,$,p){n=+n,a=+a,e=+e,p=!!p;if(e<0)throw new Error(`negative radius: ${e}`);let r=e*Math.cos(_),o=e*Math.sin(_),d=n+r,l=a+o,u=1^p,x=p?_-$:$-_;null===this._x1?this._append`M${d},${l}`:(Math.abs(this._x1-d)>i||Math.abs(this._y1-l)>i)&&this._append`L${d},${l}`;if(e){x<0&&(x=x%h+h);x>s?this._append`A${e},${e},0,1,${u},${n-r},${a-o}A${e},${e},0,1,${u},${this._x1=d},${this._y1=l}`:x>i&&this._append`A${e},${e},0,${+(x>=t)},${u},${this._x1=n+e*Math.cos($)},${this._y1=a+e*Math.sin($)}`}}rect(t,h,i,s){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+h}h${i=+i}v${+s}h${-i}Z`}toString(){return this._}}function path(){return new Path}path.prototype=Path.prototype;function pathRound(t=3){return new Path(+t)}export{Path,path,pathRound};
|
||||||
|
|
2
vendor/javascript/d3-polygon.js
vendored
Normal file
2
vendor/javascript/d3-polygon.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
function area(n){var r,e=-1,t=n.length,o=n[t-1],l=0;while(++e<t){r=o;o=n[e];l+=r[1]*o[0]-r[0]*o[1]}return l/2}function centroid(n){var r,e,t=-1,o=n.length,l=0,u=0,a=n[o-1],h=0;while(++t<o){r=a;a=n[t];h+=e=r[0]*a[1]-a[0]*r[1];l+=(r[0]+a[0])*e;u+=(r[1]+a[1])*e}return h*=3,[l/h,u/h]}function cross(n,r,e){return(r[0]-n[0])*(e[1]-n[1])-(r[1]-n[1])*(e[0]-n[0])}function lexicographicOrder(n,r){return n[0]-r[0]||n[1]-r[1]}function computeUpperHullIndexes(n){const r=n.length,e=[0,1];let t,o=2;for(t=2;t<r;++t){while(o>1&&cross(n[e[o-2]],n[e[o-1]],n[t])<=0)--o;e[o++]=t}return e.slice(0,o)}function hull(n){if((e=n.length)<3)return null;var r,e,t=new Array(e),o=new Array(e);for(r=0;r<e;++r)t[r]=[+n[r][0],+n[r][1],r];t.sort(lexicographicOrder);for(r=0;r<e;++r)o[r]=[t[r][0],-t[r][1]];var l=computeUpperHullIndexes(t),u=computeUpperHullIndexes(o);var a=u[0]===l[0],h=u[u.length-1]===l[l.length-1],i=[];for(r=l.length-1;r>=0;--r)i.push(n[t[l[r]][2]]);for(r=+a;r<u.length-h;++r)i.push(n[t[u[r]][2]]);return i}function contains(n,r){var e,t,o=n.length,l=n[o-1],u=r[0],a=r[1],h=l[0],i=l[1],c=false;for(var s=0;s<o;++s){l=n[s],e=l[0],t=l[1];t>a!==i>a&&u<(h-e)*(a-t)/(i-t)+e&&(c=!c);h=e,i=t}return c}function length(n){var r,e,t=-1,o=n.length,l=n[o-1],u=l[0],a=l[1],h=0;while(++t<o){r=u;e=a;l=n[t];u=l[0];a=l[1];r-=u;e-=a;h+=Math.hypot(r,e)}return h}export{area as polygonArea,centroid as polygonCentroid,contains as polygonContains,hull as polygonHull,length as polygonLength};
|
||||||
|
|
2
vendor/javascript/d3-quadtree.js
vendored
Normal file
2
vendor/javascript/d3-quadtree.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-random.js
vendored
Normal file
2
vendor/javascript/d3-random.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-scale-chromatic.js
vendored
Normal file
2
vendor/javascript/d3-scale-chromatic.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-scale.js
vendored
Normal file
2
vendor/javascript/d3-scale.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-selection.js
vendored
Normal file
2
vendor/javascript/d3-selection.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-shape.js
vendored
Normal file
2
vendor/javascript/d3-shape.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-time-format.js
vendored
Normal file
2
vendor/javascript/d3-time-format.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-time.js
vendored
Normal file
2
vendor/javascript/d3-time.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-timer.js
vendored
Normal file
2
vendor/javascript/d3-timer.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
var t,e,n=0,i=0,r=0,o=1e3,l=0,a=0,u=0,s="object"===typeof performance&&performance.now?performance:Date,c="object"===typeof window&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};function now(){return a||(c(clearNow),a=s.now()+u)}function clearNow(){a=0}function Timer(){this._call=this._time=this._next=null}Timer.prototype=timer.prototype={constructor:Timer,restart:function(n,i,r){if("function"!==typeof n)throw new TypeError("callback is not a function");r=(null==r?now():+r)+(null==i?0:+i);if(!this._next&&e!==this){e?e._next=this:t=this;e=this}this._call=n;this._time=r;sleep()},stop:function(){if(this._call){this._call=null;this._time=Infinity;sleep()}}};function timer(t,e,n){var i=new Timer;i.restart(t,e,n);return i}function timerFlush(){now();++n;var e,i=t;while(i){(e=a-i._time)>=0&&i._call.call(void 0,e);i=i._next}--n}function wake(){a=(l=s.now())+u;n=i=0;try{timerFlush()}finally{n=0;nap();a=0}}function poke(){var t=s.now(),e=t-l;e>o&&(u-=e,l=t)}function nap(){var n,i,r=t,o=Infinity;while(r)if(r._call){o>r._time&&(o=r._time);n=r,r=r._next}else{i=r._next,r._next=null;r=n?n._next=i:t=i}e=n;sleep(o)}function sleep(t){if(!n){i&&(i=clearTimeout(i));var e=t-a;if(e>24){t<Infinity&&(i=setTimeout(wake,t-s.now()-u));r&&(r=clearInterval(r))}else{r||(l=s.now(),r=setInterval(poke,o));n=1,c(wake)}}}function timeout(t,e,n){var i=new Timer;e=null==e?0:+e;i.restart((n=>{i.stop();t(n+e)}),e,n);return i}function interval(t,e,n){var i=new Timer,r=e;if(null==e)return i.restart(t,e,n),i;i._restart=i.restart;i.restart=function(t,e,n){e=+e,n=null==n?now():+n;i._restart((function tick(o){o+=r;i._restart(tick,r+=e,n);t(o)}),e,n)};i.restart(t,e,n);return i}export{interval,now,timeout,timer,timerFlush};
|
||||||
|
|
2
vendor/javascript/d3-transition.js
vendored
Normal file
2
vendor/javascript/d3-transition.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/d3-zoom.js
vendored
Normal file
2
vendor/javascript/d3-zoom.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
vendor/javascript/d3.js
vendored
Normal file
1
vendor/javascript/d3.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export*from"d3-array";export*from"d3-axis";export*from"d3-brush";export*from"d3-chord";export*from"d3-color";export*from"d3-contour";export*from"d3-delaunay";export*from"d3-dispatch";export*from"d3-drag";export*from"d3-dsv";export*from"d3-ease";export*from"d3-fetch";export*from"d3-force";export*from"d3-format";export*from"d3-geo";export*from"d3-hierarchy";export*from"d3-interpolate";export*from"d3-path";export*from"d3-polygon";export*from"d3-quadtree";export*from"d3-random";export*from"d3-scale";export*from"d3-scale-chromatic";export*from"d3-selection";export*from"d3-shape";export*from"d3-time";export*from"d3-time-format";export*from"d3-timer";export*from"d3-transition";export*from"d3-zoom";
|
2
vendor/javascript/delaunator.js
vendored
Normal file
2
vendor/javascript/delaunator.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
vendor/javascript/internmap.js
vendored
Normal file
2
vendor/javascript/internmap.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
class InternMap extends Map{constructor(e,t=keyof){super();Object.defineProperties(this,{_intern:{value:new Map},_key:{value:t}});if(null!=e)for(const[t,n]of e)this.set(t,n)}get(e){return super.get(intern_get(this,e))}has(e){return super.has(intern_get(this,e))}set(e,t){return super.set(intern_set(this,e),t)}delete(e){return super.delete(intern_delete(this,e))}}class InternSet extends Set{constructor(e,t=keyof){super();Object.defineProperties(this,{_intern:{value:new Map},_key:{value:t}});if(null!=e)for(const t of e)this.add(t)}has(e){return super.has(intern_get(this,e))}add(e){return super.add(intern_set(this,e))}delete(e){return super.delete(intern_delete(this,e))}}function intern_get({_intern:e,_key:t},n){const r=t(n);return e.has(r)?e.get(r):n}function intern_set({_intern:e,_key:t},n){const r=t(n);if(e.has(r))return e.get(r);e.set(r,n);return n}function intern_delete({_intern:e,_key:t},n){const r=t(n);if(e.has(r)){n=e.get(r);e.delete(r)}return n}function keyof(e){return null!==e&&"object"===typeof e?e.valueOf():e}export{InternMap,InternSet};
|
||||||
|
|
2
vendor/javascript/robust-predicates.js
vendored
Normal file
2
vendor/javascript/robust-predicates.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue