1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-22 14:49:38 +02:00

Temp fix for Stimulus charts

This commit is contained in:
Zach Gollwitzer 2024-10-11 14:40:13 -04:00
parent d9f11e002a
commit e357c0485f
2 changed files with 195 additions and 195 deletions

View file

@ -19,19 +19,19 @@ export default class extends Controller {
} }
bulkEditDrawerTitleTargetConnected(element) { bulkEditDrawerTitleTargetConnected(element) {
element.innerText = `Edit ${this.selectedIdsValue.length} ${this.#pluralizedResourceName()}` element.innerText = `Edit ${this.selectedIdsValue.length} ${this._pluralizedResourceName()}`
} }
submitBulkRequest(e) { submitBulkRequest(e) {
const form = e.target.closest("form"); const form = e.target.closest("form");
const scope = e.params.scope const scope = e.params.scope
this.#addHiddenFormInputsForSelectedIds(form, `${scope}[entry_ids][]`, this.selectedIdsValue) this._addHiddenFormInputsForSelectedIds(form, `${scope}[entry_ids][]`, this.selectedIdsValue)
form.requestSubmit() form.requestSubmit()
} }
togglePageSelection(e) { togglePageSelection(e) {
if (e.target.checked) { if (e.target.checked) {
this.#selectAll() this._selectAll()
} else { } else {
this.deselectAll() this.deselectAll()
} }
@ -40,20 +40,20 @@ export default class extends Controller {
toggleGroupSelection(e) { toggleGroupSelection(e) {
const group = this.groupTargets.find(group => group.contains(e.target)) const group = this.groupTargets.find(group => group.contains(e.target))
this.#rowsForGroup(group).forEach(row => { this._rowsForGroup(group).forEach(row => {
if (e.target.checked) { if (e.target.checked) {
this.#addToSelection(row.dataset.id) this._addToSelection(row.dataset.id)
} else { } else {
this.#removeFromSelection(row.dataset.id) this._removeFromSelection(row.dataset.id)
} }
}) })
} }
toggleRowSelection(e) { toggleRowSelection(e) {
if (e.target.checked) { if (e.target.checked) {
this.#addToSelection(e.target.dataset.id) this._addToSelection(e.target.dataset.id)
} else { } else {
this.#removeFromSelection(e.target.dataset.id) this._removeFromSelection(e.target.dataset.id)
} }
} }
@ -66,8 +66,8 @@ export default class extends Controller {
this._updateView() this._updateView()
} }
#addHiddenFormInputsForSelectedIds(form, paramName, transactionIds) { _addHiddenFormInputsForSelectedIds(form, paramName, transactionIds) {
this.#resetFormInputs(form, paramName); this._resetFormInputs(form, paramName);
transactionIds.forEach(id => { transactionIds.forEach(id => {
const input = document.createElement("input"); const input = document.createElement("input");
@ -78,47 +78,47 @@ export default class extends Controller {
}) })
} }
#resetFormInputs(form, paramName) { _resetFormInputs(form, paramName) {
const existingInputs = form.querySelectorAll(`input[name='${paramName}']`); const existingInputs = form.querySelectorAll(`input[name='${paramName}']`);
existingInputs.forEach((input) => input.remove()); existingInputs.forEach((input) => input.remove());
} }
#rowsForGroup(group) { _rowsForGroup(group) {
return this.rowTargets.filter(row => group.contains(row)) return this.rowTargets.filter(row => group.contains(row))
} }
#addToSelection(idToAdd) { _addToSelection(idToAdd) {
this.selectedIdsValue = Array.from( this.selectedIdsValue = Array.from(
new Set([...this.selectedIdsValue, idToAdd]) new Set([...this.selectedIdsValue, idToAdd])
) )
} }
#removeFromSelection(idToRemove) { _removeFromSelection(idToRemove) {
this.selectedIdsValue = this.selectedIdsValue.filter(id => id !== idToRemove) this.selectedIdsValue = this.selectedIdsValue.filter(id => id !== idToRemove)
} }
#selectAll() { _selectAll() {
this.selectedIdsValue = this.rowTargets.map(t => t.dataset.id) this.selectedIdsValue = this.rowTargets.map(t => t.dataset.id)
} }
_updateView = () => { _updateView = () => {
this.#updateSelectionBar() this._updateSelectionBar()
this.#updateGroups() this._updateGroups()
this.#updateRows() this._updateRows()
} }
#updateSelectionBar() { _updateSelectionBar() {
const count = this.selectedIdsValue.length const count = this.selectedIdsValue.length
this.selectionBarTextTarget.innerText = `${count} ${this.#pluralizedResourceName()} selected` this.selectionBarTextTarget.innerText = `${count} ${this._pluralizedResourceName()} selected`
this.selectionBarTarget.hidden = count === 0 this.selectionBarTarget.hidden = count === 0
this.selectionBarTarget.querySelector("input[type='checkbox']").checked = count > 0 this.selectionBarTarget.querySelector("input[type='checkbox']").checked = count > 0
} }
#pluralizedResourceName() { _pluralizedResourceName() {
return `${this.resourceValue}${this.selectedIdsValue.length === 1 ? "" : "s"}` return `${this.resourceValue}${this.selectedIdsValue.length === 1 ? "" : "s"}`
} }
#updateGroups() { _updateGroups() {
this.groupTargets.forEach(group => { this.groupTargets.forEach(group => {
const rows = this.rowTargets.filter(row => group.contains(row)) const rows = this.rowTargets.filter(row => group.contains(row))
const groupSelected = rows.length > 0 && rows.every(row => this.selectedIdsValue.includes(row.dataset.id)) const groupSelected = rows.length > 0 && rows.every(row => this.selectedIdsValue.includes(row.dataset.id))
@ -126,7 +126,7 @@ export default class extends Controller {
}) })
} }
#updateRows() { _updateRows() {
this.rowTargets.forEach(row => { this.rowTargets.forEach(row => {
row.checked = this.selectedIdsValue.includes(row.dataset.id) row.checked = this.selectedIdsValue.includes(row.dataset.id)
}) })

View file

@ -11,47 +11,47 @@ export default class extends Controller {
usePercentSign: Boolean usePercentSign: Boolean
} }
#d3SvgMemo = null; _d3SvgMemo = null;
#d3GroupMemo = null; _d3GroupMemo = null;
#d3Tooltip = null; _d3Tooltip = null;
#d3InitialContainerWidth = 0; _d3InitialContainerWidth = 0;
#d3InitialContainerHeight = 0; _d3InitialContainerHeight = 0;
#normalDataPoints = []; _normalDataPoints = [];
connect() { connect() {
this.#install() this._install()
document.addEventListener("turbo:load", this.#reinstall) document.addEventListener("turbo:load", this._reinstall)
} }
disconnect() { disconnect() {
this.#teardown() this._teardown()
document.removeEventListener("turbo:load", this.#reinstall) document.removeEventListener("turbo:load", this._reinstall)
} }
#reinstall = () => { _reinstall = () => {
this.#teardown() this._teardown()
this.#install() this._install()
} }
#teardown() { _teardown() {
this.#d3SvgMemo = null this._d3SvgMemo = null
this.#d3GroupMemo = null this._d3GroupMemo = null
this.#d3Tooltip = null this._d3Tooltip = null
this.#normalDataPoints = [] this._normalDataPoints = []
this.#d3Container.selectAll("*").remove() this._d3Container.selectAll("*").remove()
} }
#install() { _install() {
this.#normalizeDataPoints() this._normalizeDataPoints()
this.#rememberInitialContainerSize() this._rememberInitialContainerSize()
this.#draw() this._draw()
} }
#normalizeDataPoints() { _normalizeDataPoints() {
this.#normalDataPoints = (this.dataValue.values || []).map((d) => ({ this._normalDataPoints = (this.dataValue.values || []).map((d) => ({
...d, ...d,
date: new Date(d.date), date: new Date(d.date),
value: d.value.amount ? +d.value.amount : +d.value, value: d.value.amount ? +d.value.amount : +d.value,
@ -60,96 +60,96 @@ export default class extends Controller {
} }
#rememberInitialContainerSize() { _rememberInitialContainerSize() {
this.#d3InitialContainerWidth = this.#d3Container.node().clientWidth this._d3InitialContainerWidth = this._d3Container.node().clientWidth
this.#d3InitialContainerHeight = this.#d3Container.node().clientHeight this._d3InitialContainerHeight = this._d3Container.node().clientHeight
} }
#draw() { _draw() {
if (this.#normalDataPoints.length < 2) { if (this._normalDataPoints.length < 2) {
this.#drawEmpty() this._drawEmpty()
} else { } else {
this.#drawChart() this._drawChart()
} }
} }
#drawEmpty() { _drawEmpty() {
this.#d3Svg.selectAll(".tick").remove() this._d3Svg.selectAll(".tick").remove()
this.#d3Svg.selectAll(".domain").remove() this._d3Svg.selectAll(".domain").remove()
this.#drawDashedLineEmptyState() this._drawDashedLineEmptyState()
this.#drawCenteredCircleEmptyState() this._drawCenteredCircleEmptyState()
} }
#drawDashedLineEmptyState() { _drawDashedLineEmptyState() {
this.#d3Svg this._d3Svg
.append("line") .append("line")
.attr("x1", this.#d3InitialContainerWidth / 2) .attr("x1", this._d3InitialContainerWidth / 2)
.attr("y1", 0) .attr("y1", 0)
.attr("x2", this.#d3InitialContainerWidth / 2) .attr("x2", this._d3InitialContainerWidth / 2)
.attr("y2", this.#d3InitialContainerHeight) .attr("y2", this._d3InitialContainerHeight)
.attr("stroke", tailwindColors.gray[300]) .attr("stroke", tailwindColors.gray[300])
.attr("stroke-dasharray", "4, 4") .attr("stroke-dasharray", "4, 4")
} }
#drawCenteredCircleEmptyState() { _drawCenteredCircleEmptyState() {
this.#d3Svg this._d3Svg
.append("circle") .append("circle")
.attr("cx", this.#d3InitialContainerWidth / 2) .attr("cx", this._d3InitialContainerWidth / 2)
.attr("cy", this.#d3InitialContainerHeight / 2) .attr("cy", this._d3InitialContainerHeight / 2)
.attr("r", 4) .attr("r", 4)
.style("fill", tailwindColors.gray[400]) .style("fill", tailwindColors.gray[400])
} }
#drawChart() { _drawChart() {
this.#drawTrendline() this._drawTrendline()
if (this.useLabelsValue) { if (this.useLabelsValue) {
this.#drawXAxisLabels() this._drawXAxisLabels()
this.#drawGradientBelowTrendline() this._drawGradientBelowTrendline()
} }
if (this.useTooltipValue) { if (this.useTooltipValue) {
this.#drawTooltip() this._drawTooltip()
this.#trackMouseForShowingTooltip() this._trackMouseForShowingTooltip()
} }
} }
#drawTrendline() { _drawTrendline() {
this.#installTrendlineSplit() this._installTrendlineSplit()
this.#d3Group this._d3Group
.append("path") .append("path")
.datum(this.#normalDataPoints) .datum(this._normalDataPoints)
.attr("fill", "none") .attr("fill", "none")
.attr("stroke", `url(#${this.element.id}-split-gradient)`) .attr("stroke", `url(#${this.element.id}-split-gradient)`)
.attr("d", this.#d3Line) .attr("d", this._d3Line)
.attr("stroke-linejoin", "round") .attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round") .attr("stroke-linecap", "round")
.attr("stroke-width", this.strokeWidthValue) .attr("stroke-width", this.strokeWidthValue)
} }
#installTrendlineSplit() { _installTrendlineSplit() {
const gradient = this.#d3Svg const gradient = this._d3Svg
.append("defs") .append("defs")
.append("linearGradient") .append("linearGradient")
.attr("id", `${this.element.id}-split-gradient`) .attr("id", `${this.element.id}-split-gradient`)
.attr("gradientUnits", "userSpaceOnUse") .attr("gradientUnits", "userSpaceOnUse")
.attr("x1", this.#d3XScale.range()[0]) .attr("x1", this._d3XScale.range()[0])
.attr("x2", this.#d3XScale.range()[1]) .attr("x2", this._d3XScale.range()[1])
gradient.append("stop") gradient.append("stop")
.attr("class", "start-color") .attr("class", "start-color")
.attr("offset", "0%") .attr("offset", "0%")
.attr("stop-color", this.#trendColor) .attr("stop-color", this._trendColor)
gradient.append("stop") gradient.append("stop")
.attr("class", "middle-color") .attr("class", "middle-color")
.attr("offset", "100%") .attr("offset", "100%")
.attr("stop-color", this.#trendColor) .attr("stop-color", this._trendColor)
gradient.append("stop") gradient.append("stop")
.attr("class", "end-color") .attr("class", "end-color")
@ -157,31 +157,31 @@ export default class extends Controller {
.attr("stop-color", tailwindColors.gray[300]) .attr("stop-color", tailwindColors.gray[300])
} }
#setTrendlineSplitAt(percent) { _setTrendlineSplitAt(percent) {
this.#d3Svg this._d3Svg
.select(`#${this.element.id}-split-gradient`) .select(`#${this.element.id}-split-gradient`)
.select(".middle-color") .select(".middle-color")
.attr("offset", `${percent * 100}%`) .attr("offset", `${percent * 100}%`)
this.#d3Svg this._d3Svg
.select(`#${this.element.id}-split-gradient`) .select(`#${this.element.id}-split-gradient`)
.select(".end-color") .select(".end-color")
.attr("offset", `${percent * 100}%`) .attr("offset", `${percent * 100}%`)
this.#d3Svg this._d3Svg
.select(`#${this.element.id}-trendline-gradient-rect`) .select(`#${this.element.id}-trendline-gradient-rect`)
.attr("width", this.#d3ContainerWidth * percent) .attr("width", this._d3ContainerWidth * percent)
} }
#drawXAxisLabels() { _drawXAxisLabels() {
// Add ticks // Add ticks
this.#d3Group this._d3Group
.append("g") .append("g")
.attr("transform", `translate(0,${this.#d3ContainerHeight})`) .attr("transform", `translate(0,${this._d3ContainerHeight})`)
.call( .call(
d3 d3
.axisBottom(this.#d3XScale) .axisBottom(this._d3XScale)
.tickValues([this.#normalDataPoints[0].date, this.#normalDataPoints[this.#normalDataPoints.length - 1].date]) .tickValues([this._normalDataPoints[0].date, this._normalDataPoints[this._normalDataPoints.length - 1].date])
.tickSize(0) .tickSize(0)
.tickFormat(d3.timeFormat("%d %b %Y")) .tickFormat(d3.timeFormat("%d %b %Y"))
) )
@ -189,7 +189,7 @@ export default class extends Controller {
.remove() .remove()
// Style ticks // Style ticks
this.#d3Group.selectAll(".tick text") this._d3Group.selectAll(".tick text")
.style("fill", tailwindColors.gray[500]) .style("fill", tailwindColors.gray[500])
.style("font-size", "12px") .style("font-size", "12px")
.style("font-weight", "500") .style("font-weight", "500")
@ -201,54 +201,54 @@ export default class extends Controller {
.attr("dy", "0em") .attr("dy", "0em")
} }
#drawGradientBelowTrendline() { _drawGradientBelowTrendline() {
// Define gradient // Define gradient
const gradient = this.#d3Group const gradient = this._d3Group
.append("defs") .append("defs")
.append("linearGradient") .append("linearGradient")
.attr("id", `${this.element.id}-trendline-gradient`) .attr("id", `${this.element.id}-trendline-gradient`)
.attr("gradientUnits", "userSpaceOnUse") .attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0) .attr("x1", 0)
.attr("x2", 0) .attr("x2", 0)
.attr("y1", this.#d3YScale(d3.max(this.#normalDataPoints, d => d.value))) .attr("y1", this._d3YScale(d3.max(this._normalDataPoints, d => d.value)))
.attr("y2", this.#d3ContainerHeight) .attr("y2", this._d3ContainerHeight)
gradient gradient
.append("stop") .append("stop")
.attr("offset", 0) .attr("offset", 0)
.attr("stop-color", this.#trendColor) .attr("stop-color", this._trendColor)
.attr("stop-opacity", 0.06) .attr("stop-opacity", 0.06)
gradient gradient
.append("stop") .append("stop")
.attr("offset", 0.5) .attr("offset", 0.5)
.attr("stop-color", this.#trendColor) .attr("stop-color", this._trendColor)
.attr("stop-opacity", 0) .attr("stop-opacity", 0)
// Clip path makes gradient start at the trendline // Clip path makes gradient start at the trendline
this.#d3Group this._d3Group
.append("clipPath") .append("clipPath")
.attr("id", `${this.element.id}-clip-below-trendline`) .attr("id", `${this.element.id}-clip-below-trendline`)
.append("path") .append("path")
.datum(this.#normalDataPoints) .datum(this._normalDataPoints)
.attr("d", d3.area() .attr("d", d3.area()
.x(d => this.#d3XScale(d.date)) .x(d => this._d3XScale(d.date))
.y0(this.#d3ContainerHeight) .y0(this._d3ContainerHeight)
.y1(d => this.#d3YScale(d.value)) .y1(d => this._d3YScale(d.value))
) )
// Apply the gradient + clip path // Apply the gradient + clip path
this.#d3Group this._d3Group
.append("rect") .append("rect")
.attr("id", `${this.element.id}-trendline-gradient-rect`) .attr("id", `${this.element.id}-trendline-gradient-rect`)
.attr("width", this.#d3ContainerWidth) .attr("width", this._d3ContainerWidth)
.attr("height", this.#d3ContainerHeight) .attr("height", this._d3ContainerHeight)
.attr("clip-path", `url(#${this.element.id}-clip-below-trendline)`) .attr("clip-path", `url(#${this.element.id}-clip-below-trendline)`)
.style("fill", `url(#${this.element.id}-trendline-gradient)`) .style("fill", `url(#${this.element.id}-trendline-gradient)`)
} }
#drawTooltip() { _drawTooltip() {
this.#d3Tooltip = d3 this._d3Tooltip = d3
.select(`#${this.element.id}`) .select(`#${this.element.id}`)
.append("div") .append("div")
.style("position", "absolute") .style("position", "absolute")
@ -261,13 +261,13 @@ export default class extends Controller {
.style("opacity", 0) // Starts as hidden .style("opacity", 0) // Starts as hidden
} }
#trackMouseForShowingTooltip() { _trackMouseForShowingTooltip() {
const bisectDate = d3.bisector(d => d.date).left const bisectDate = d3.bisector(d => d.date).left
this.#d3Group this._d3Group
.append("rect") .append("rect")
.attr("width", this.#d3ContainerWidth) .attr("width", this._d3ContainerWidth)
.attr("height", this.#d3ContainerHeight) .attr("height", this._d3ContainerHeight)
.attr("fill", "none") .attr("fill", "none")
.attr("pointer-events", "all") .attr("pointer-events", "all")
.on("mousemove", (event) => { .on("mousemove", (event) => {
@ -278,53 +278,53 @@ export default class extends Controller {
const adjustedX = overflowX > 0 ? event.pageX - overflowX - 20 : tooltipX const adjustedX = overflowX > 0 ? event.pageX - overflowX - 20 : tooltipX
const [xPos] = d3.pointer(event) const [xPos] = d3.pointer(event)
const x0 = bisectDate(this.#normalDataPoints, this.#d3XScale.invert(xPos), 1) const x0 = bisectDate(this._normalDataPoints, this._d3XScale.invert(xPos), 1)
const d0 = this.#normalDataPoints[x0 - 1] const d0 = this._normalDataPoints[x0 - 1]
const d1 = this.#normalDataPoints[x0] const d1 = this._normalDataPoints[x0]
const d = xPos - this.#d3XScale(d0.date) > this.#d3XScale(d1.date) - xPos ? d1 : d0 const d = xPos - this._d3XScale(d0.date) > this._d3XScale(d1.date) - xPos ? d1 : d0
const xPercent = this.#d3XScale(d.date) / this.#d3ContainerWidth const xPercent = this._d3XScale(d.date) / this._d3ContainerWidth
this.#setTrendlineSplitAt(xPercent) this._setTrendlineSplitAt(xPercent)
// Reset // Reset
this.#d3Group.selectAll(".data-point-circle").remove() this._d3Group.selectAll(".data-point-circle").remove()
this.#d3Group.selectAll(".guideline").remove() this._d3Group.selectAll(".guideline").remove()
// Guideline // Guideline
this.#d3Group this._d3Group
.append("line") .append("line")
.attr("class", "guideline") .attr("class", "guideline")
.attr("x1", this.#d3XScale(d.date)) .attr("x1", this._d3XScale(d.date))
.attr("y1", 0) .attr("y1", 0)
.attr("x2", this.#d3XScale(d.date)) .attr("x2", this._d3XScale(d.date))
.attr("y2", this.#d3ContainerHeight) .attr("y2", this._d3ContainerHeight)
.attr("stroke", tailwindColors.gray[300]) .attr("stroke", tailwindColors.gray[300])
.attr("stroke-dasharray", "4, 4") .attr("stroke-dasharray", "4, 4")
// Big circle // Big circle
this.#d3Group this._d3Group
.append("circle") .append("circle")
.attr("class", "data-point-circle") .attr("class", "data-point-circle")
.attr("cx", this.#d3XScale(d.date)) .attr("cx", this._d3XScale(d.date))
.attr("cy", this.#d3YScale(d.value)) .attr("cy", this._d3YScale(d.value))
.attr("r", 8) .attr("r", 8)
.attr("fill", this.#trendColor) .attr("fill", this._trendColor)
.attr("fill-opacity", "0.1") .attr("fill-opacity", "0.1")
.attr("pointer-events", "none") .attr("pointer-events", "none")
// Small circle // Small circle
this.#d3Group this._d3Group
.append("circle") .append("circle")
.attr("class", "data-point-circle") .attr("class", "data-point-circle")
.attr("cx", this.#d3XScale(d.date)) .attr("cx", this._d3XScale(d.date))
.attr("cy", this.#d3YScale(d.value)) .attr("cy", this._d3YScale(d.value))
.attr("r", 3) .attr("r", 3)
.attr("fill", this.#trendColor) .attr("fill", this._trendColor)
.attr("pointer-events", "none") .attr("pointer-events", "none")
// Render tooltip // Render tooltip
this.#d3Tooltip this._d3Tooltip
.html(this.#tooltipTemplate(d)) .html(this._tooltipTemplate(d))
.style("opacity", 1) .style("opacity", 1)
.style("z-index", 999) .style("z-index", 999)
.style("left", adjustedX + "px") .style("left", adjustedX + "px")
@ -334,16 +334,16 @@ export default class extends Controller {
const hoveringOnGuideline = event.toElement?.classList.contains("guideline") const hoveringOnGuideline = event.toElement?.classList.contains("guideline")
if (!hoveringOnGuideline) { if (!hoveringOnGuideline) {
this.#d3Group.selectAll(".guideline").remove() this._d3Group.selectAll(".guideline").remove()
this.#d3Group.selectAll(".data-point-circle").remove() this._d3Group.selectAll(".data-point-circle").remove()
this.#d3Tooltip.style("opacity", 0) this._d3Tooltip.style("opacity", 0)
this.#setTrendlineSplitAt(1) this._setTrendlineSplitAt(1)
} }
}) })
} }
#tooltipTemplate(datum) { _tooltipTemplate(datum) {
return (` return (`
<div style="margin-bottom: 4px; color: ${tailwindColors.gray[500]};"> <div style="margin-bottom: 4px; color: ${tailwindColors.gray[500]};">
${d3.timeFormat("%b %d, %Y")(datum.date)} ${d3.timeFormat("%b %d, %Y")(datum.date)}
@ -356,26 +356,26 @@ export default class extends Controller {
cx="5" cx="5"
cy="5" cy="5"
r="4" r="4"
stroke="${this.#tooltipTrendColor(datum)}" stroke="${this._tooltipTrendColor(datum)}"
fill="transparent" fill="transparent"
stroke-width="1"></circle> stroke-width="1"></circle>
</svg> </svg>
${this.#tooltipValue(datum)}${this.usePercentSignValue ? "%" : ""} ${this._tooltipValue(datum)}${this.usePercentSignValue ? "%" : ""}
</div> </div>
${this.usePercentSignValue || datum.trend.value === 0 || datum.trend.value.amount === 0 ? ` ${this.usePercentSignValue || datum.trend.value === 0 || datum.trend.value.amount === 0 ? `
<span style="width: 80px;"></span> <span style="width: 80px;"></span>
` : ` ` : `
<span style="color: ${this.#tooltipTrendColor(datum)};"> <span style="color: ${this._tooltipTrendColor(datum)};">
${this.#tooltipChange(datum)} (${datum.trend.percent}%) ${this._tooltipChange(datum)} (${datum.trend.percent}%)
</span> </span>
`} `}
</div> </div>
`) `)
} }
#tooltipTrendColor(datum) { _tooltipTrendColor(datum) {
return { return {
up: tailwindColors.success, up: tailwindColors.success,
down: tailwindColors.error, down: tailwindColors.error,
@ -383,30 +383,30 @@ export default class extends Controller {
}[datum.trend.direction] }[datum.trend.direction]
} }
#tooltipValue(datum) { _tooltipValue(datum) {
if (datum.currency) { if (datum.currency) {
return this.#currencyValue(datum) return this._currencyValue(datum)
} else { } else {
return datum.value return datum.value
} }
} }
#tooltipChange(datum) { _tooltipChange(datum) {
if (datum.currency) { if (datum.currency) {
return this.#currencyChange(datum) return this._currencyChange(datum)
} else { } else {
return this.#decimalChange(datum) return this._decimalChange(datum)
} }
} }
#currencyValue(datum) { _currencyValue(datum) {
return Intl.NumberFormat(undefined, { return Intl.NumberFormat(undefined, {
style: "currency", style: "currency",
currency: datum.currency, currency: datum.currency,
}).format(datum.value) }).format(datum.value)
} }
#currencyChange(datum) { _currencyChange(datum) {
return Intl.NumberFormat(undefined, { return Intl.NumberFormat(undefined, {
style: "currency", style: "currency",
currency: datum.currency, currency: datum.currency,
@ -414,7 +414,7 @@ export default class extends Controller {
}).format(datum.trend.value.amount) }).format(datum.trend.value.amount)
} }
#decimalChange(datum) { _decimalChange(datum) {
return Intl.NumberFormat(undefined, { return Intl.NumberFormat(undefined, {
style: "decimal", style: "decimal",
signDisplay: "always", signDisplay: "always",
@ -422,38 +422,38 @@ export default class extends Controller {
} }
#createMainSvg() { _createMainSvg() {
return this.#d3Container return this._d3Container
.append("svg") .append("svg")
.attr("width", this.#d3InitialContainerWidth) .attr("width", this._d3InitialContainerWidth)
.attr("height", this.#d3InitialContainerHeight) .attr("height", this._d3InitialContainerHeight)
.attr("viewBox", [0, 0, this.#d3InitialContainerWidth, this.#d3InitialContainerHeight]) .attr("viewBox", [0, 0, this._d3InitialContainerWidth, this._d3InitialContainerHeight])
} }
#createMainGroup() { _createMainGroup() {
return this.#d3Svg return this._d3Svg
.append("g") .append("g")
.attr("transform", `translate(${this.#margin.left},${this.#margin.top})`) .attr("transform", `translate(${this._margin.left},${this._margin.top})`)
} }
get #d3Svg() { get _d3Svg() {
if (this.#d3SvgMemo) { if (this._d3SvgMemo) {
return this.#d3SvgMemo return this._d3SvgMemo
} else { } else {
return this.#d3SvgMemo = this.#createMainSvg() return this._d3SvgMemo = this._createMainSvg()
} }
} }
get #d3Group() { get _d3Group() {
if (this.#d3GroupMemo) { if (this._d3GroupMemo) {
return this.#d3GroupMemo return this._d3GroupMemo
} else { } else {
return this.#d3GroupMemo = this.#createMainGroup() return this._d3GroupMemo = this._createMainGroup()
} }
} }
get #margin() { get _margin() {
if (this.useLabelsValue) { if (this.useLabelsValue) {
return { top: 20, right: 0, bottom: 30, left: 0 } return { top: 20, right: 0, bottom: 30, left: 0 }
} else { } else {
@ -461,59 +461,59 @@ export default class extends Controller {
} }
} }
get #d3ContainerWidth() { get _d3ContainerWidth() {
return this.#d3InitialContainerWidth - this.#margin.left - this.#margin.right return this._d3InitialContainerWidth - this._margin.left - this._margin.right
} }
get #d3ContainerHeight() { get _d3ContainerHeight() {
return this.#d3InitialContainerHeight - this.#margin.top - this.#margin.bottom return this._d3InitialContainerHeight - this._margin.top - this._margin.bottom
} }
get #d3Container() { get _d3Container() {
return d3.select(this.element) return d3.select(this.element)
} }
get #trendColor() { get _trendColor() {
if (this.#trendDirection === "flat") { if (this._trendDirection === "flat") {
return tailwindColors.gray[500] return tailwindColors.gray[500]
} else if (this.#trendDirection === this.#favorableDirection) { } else if (this._trendDirection === this._favorableDirection) {
return tailwindColors.green[500] return tailwindColors.green[500]
} else { } else {
return tailwindColors.error return tailwindColors.error
} }
} }
get #trendDirection() { get _trendDirection() {
return this.dataValue.trend.direction return this.dataValue.trend.direction
} }
get #favorableDirection() { get _favorableDirection() {
return this.dataValue.trend.favorable_direction return this.dataValue.trend.favorable_direction
} }
get #d3Line() { get _d3Line() {
return d3 return d3
.line() .line()
.x(d => this.#d3XScale(d.date)) .x(d => this._d3XScale(d.date))
.y(d => this.#d3YScale(d.value)) .y(d => this._d3YScale(d.value))
} }
get #d3XScale() { get _d3XScale() {
return d3 return d3
.scaleTime() .scaleTime()
.rangeRound([0, this.#d3ContainerWidth]) .rangeRound([0, this._d3ContainerWidth])
.domain(d3.extent(this.#normalDataPoints, d => d.date)) .domain(d3.extent(this._normalDataPoints, d => d.date))
} }
get #d3YScale() { get _d3YScale() {
const reductionPercent = this.useLabelsValue ? 0.15 : 0.05 const reductionPercent = this.useLabelsValue ? 0.15 : 0.05
const dataMin = d3.min(this.#normalDataPoints, d => d.value) const dataMin = d3.min(this._normalDataPoints, d => d.value)
const dataMax = d3.max(this.#normalDataPoints, d => d.value) const dataMax = d3.max(this._normalDataPoints, d => d.value)
const padding = (dataMax - dataMin) * reductionPercent const padding = (dataMax - dataMin) * reductionPercent
return d3 return d3
.scaleLinear() .scaleLinear()
.rangeRound([this.#d3ContainerHeight, 0]) .rangeRound([this._d3ContainerHeight, 0])
.domain([dataMin - padding, dataMax + padding]) .domain([dataMin - padding, dataMax + padding])
} }
} }