Skip to content

Commit

Permalink
Feat: Move utils to modules for better minification
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenhutchings committed Apr 28, 2023
1 parent 3524764 commit 43e846a
Show file tree
Hide file tree
Showing 15 changed files with 155 additions and 156 deletions.
4 changes: 2 additions & 2 deletions src/lib/map.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { get as getColor, wrap as wrapColor } from "./color.js"
import utils from "./utils.js"
import decimalPlaces from "./utils/decimal-places.js"

/**
* To render a chart, the data you supply is mapped to various
Expand Down Expand Up @@ -127,7 +127,7 @@ const Map = function (
}

const values = data.map(map.y || map.value)
const places = Math.min(Math.max(...values.map(utils.decimalPlaces)), 2)
const places = Math.min(Math.max(...values.map(decimalPlaces)), 2)

// By default, a label will only show when it exceeds the minimum value
// specified by a chart. It uses the largest number of decimal places found
Expand Down
73 changes: 0 additions & 73 deletions src/lib/utils.js

This file was deleted.

3 changes: 3 additions & 0 deletions src/lib/utils/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const EPSILON = Number.EPSILON

export const DEFAULT_PRECISION = Math.floor(Math.abs(Math.log10(EPSILON)))
20 changes: 20 additions & 0 deletions src/lib/utils/decimal-places.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DEFAULT_PRECISION } from "./constants.js"
import toPrecision from "./to-precision.js"

/**
* Counts the number of decimal places specified by a number
* @private
* @param {number} value
* @returns {number} decimals places
*/
export default (n, precision = DEFAULT_PRECISION) => {
let count = 0
n = Math.abs(n)

while (toPrecision(n % 1, precision) > 0 && count < precision) {
count++
n *= 10
}

return count
}
7 changes: 7 additions & 0 deletions src/lib/utils/magnitude.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Calculate the magnitude of a number (power of 10)
* @private
* @param {number} n
* @returns {number}
*/
export default (n) => Math.floor(Math.log10(Math.abs(n)))
10 changes: 10 additions & 0 deletions src/lib/utils/percent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Convert a value between 0 and 1 to a percentage string. The suffix is ignored
* for zero. The precision is limited to reduce the size of the rendered SVG.
* @private
* @param {number} value
* @param {string} [units]
* @returns {string} percent
*/
export default (value, units = "%") =>
value !== 0 ? +(value * 100).toFixed(2) + units : "0"
15 changes: 15 additions & 0 deletions src/lib/utils/sum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DEFAULT_PRECISION } from "./constants.js"

/**
* Add the values in an array, reading `item.value` where present.
* @private
* @param {Array} array
* @returns {number} total
*/
export default (array, precision = DEFAULT_PRECISION) => {
const f = Math.pow(10, precision)
return array.reduce(
(m, v = 0) => (f * m + f * ((v && v.value) || +v || 0)) / f,
0
)
}
14 changes: 14 additions & 0 deletions src/lib/utils/to-precision.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DEFAULT_PRECISION } from "./constants.js"

/**
* Convert a value to a specified precision to avoid floating point errors
* @private
* @param {number} value
* @param {number} [precision]
* @returns {number} value
*/
export default (value, precision = DEFAULT_PRECISION) => {
const f = Math.pow(10, precision)
const n = precision < 0 ? value : 0.01 / f + value
return Math.round(n * f) / f
}
47 changes: 24 additions & 23 deletions src/templates/axis.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import $ from "../lib/dom/index.js"
import utils from "../lib/utils.js"

import decimalPlaces from "../lib/utils/decimal-places.js"
import magnitude from "../lib/utils/magnitude.js"
import percent from "../lib/utils/percent.js"
import toPrecision from "../lib/utils/to-precision.js"

// The number of ticks to use, in preferential order.
const TICKCOUNT_ORDER = [5, 6, 7, 8, 4, 9, 3, 10, 11, 12, 13]
Expand Down Expand Up @@ -53,10 +57,10 @@ const pad = (t, inset = 0) => inset + (1 - inset * 2) * t
* @returns {number} scale
*/
const getScale = (min, max) => {
let m = Math.max(utils.magnitude(min), utils.magnitude(max))
let m = Math.max(magnitude(min), magnitude(max))

// The magnitude should be no greater than the difference
m = Math.min(m, utils.magnitude(max - min))
m = Math.min(m, magnitude(max - min))

// Deal with integers from here
let scale = Math.pow(10, m)
Expand Down Expand Up @@ -108,8 +112,8 @@ const getScale = (min, max) => {
const getBounds = (values) => {
if (values.length === 0) return [0, 1]

let min = utils.toPrecision(Math.min(...values), MAX_PRECISION)
let max = utils.toPrecision(Math.max(...values), MAX_PRECISION)
let min = toPrecision(Math.min(...values), MAX_PRECISION)
let max = toPrecision(Math.max(...values), MAX_PRECISION)

if (min === max) {
// All values are zero
Expand Down Expand Up @@ -139,8 +143,8 @@ const getBounds = (values) => {
}

return [
utils.toPrecision(min * f, MAX_PRECISION),
utils.toPrecision(max * f, MAX_PRECISION),
toPrecision(min * f, MAX_PRECISION),
toPrecision(max * f, MAX_PRECISION),
]
}

Expand All @@ -159,8 +163,8 @@ const getTicks = (min, max) => {

const f = getScale(min, max)

min = utils.toPrecision(min / f, MAX_PRECISION)
max = utils.toPrecision(max / f, MAX_PRECISION)
min = toPrecision(min / f, MAX_PRECISION)
max = toPrecision(max / f, MAX_PRECISION)

let d = max - min

Expand All @@ -172,15 +176,15 @@ const getTicks = (min, max) => {
})
}

const maxDecimals = utils.decimalPlaces(utils.toPrecision(d, MAX_PRECISION))
const maxDecimals = decimalPlaces(toPrecision(d, MAX_PRECISION))

return (
TICKCOUNT_ORDER.find((n) => {
const mod = utils.toPrecision(d / (n - 1), MAX_PRECISION)
const dec = utils.toPrecision(mod, MAX_PRECISION)
const mod = toPrecision(d / (n - 1), MAX_PRECISION)
const dec = toPrecision(mod, MAX_PRECISION)

return (
utils.decimalPlaces(dec) <= maxDecimals &&
decimalPlaces(dec) <= maxDecimals &&
(min > 0 || max < 0 || (min % mod === 0 && max % mod === 0))
)
}) || 2
Expand Down Expand Up @@ -243,7 +247,7 @@ export const setup = (axis = {}, data, guessBounds = true) => {
label = (v, i) =>
(ticks < 8 || i % 2 === 0) &&
Math.abs(v).toString().length <= length &&
utils.toPrecision(v, MAX_PRECISION)
toPrecision(v, MAX_PRECISION)
}

// If the label is an array, wrap in a function
Expand Down Expand Up @@ -306,16 +310,16 @@ export default (type, axis) => {

const line = (t, className, d) => {
const props = { class: className }
const v = t !== 0 && utils.percent(t)
const v = t !== 0 && percent(t)

if (type === "x") {
props.x1 = v
props.x2 = v
props.y2 = utils.percent(1)
props.y2 = percent(1)
} else {
props.y1 = v
props.y2 = v
props.x2 = utils.percent(1)
props.x2 = percent(1)
}

if (d) {
Expand All @@ -329,10 +333,7 @@ export default (type, axis) => {
if (axis.hasSeries && type === "x") txtProps.dy = "3em"

const children = axis.grid.map((t, i) => {
const v = utils.toPrecision(
axis.min + (axis.max - axis.min) * t,
MAX_PRECISION
)
const v = toPrecision(axis.min + (axis.max - axis.min) * t, MAX_PRECISION)
const lines = []

if (axis.label) {
Expand Down Expand Up @@ -365,8 +366,8 @@ export default (type, axis) => {

return $.svg(
type === "x"
? { x: utils.percent(pad(t, axis.inset)) }
: { y: utils.percent(pad(1 - t, axis.inset)) }
? { x: percent(pad(t, axis.inset)) }
: { y: percent(pad(1 - t, axis.inset)) }
)(lines)
})

Expand Down
35 changes: 16 additions & 19 deletions src/templates/bar.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import $ from "../lib/dom/index.js"
import { get as getColor } from "../lib/color.js"
import utils from "../lib/utils.js"
import sum from "../lib/utils/sum.js"
import percent from "../lib/utils/percent.js"
import Map from "../lib/map.js"
import legendTemplate from "./legend.js"
import { default as axisTemplate, setup as setupAxis } from "./axis.js"
Expand Down Expand Up @@ -154,14 +155,14 @@ export default ({
width: 0.6,
...map,
},
stack ? data.flat().map((d) => utils.sum(d)) : data.flat(2),
stack ? data.flat().map((d) => sum(d)) : data.flat(2),
{ minValue: 0.05 }
)

data = map(data)

const maxWidth = Math.max(...data.flat(2).map((d) => d.width))
const values = data.flat().map((d) => utils.sum(d))
const values = data.flat().map((d) => sum(d))

xAxis = {
ticks: data.length,
Expand Down Expand Up @@ -190,45 +191,41 @@ export default ({
const bars = $.svg({ class: "values" })(
data.map((series, k) =>
$.svg({
x: data.length > 1 && utils.percent(axes.x.scale(k - 0.5)),
width: utils.percent(
data.length > 1 ? axes.x.scale(1) - axes.x.scale(0) : 1
),
x: data.length > 1 && percent(axes.x.scale(k - 0.5)),
width: percent(data.length > 1 ? axes.x.scale(1) - axes.x.scale(0) : 1),
class: ["group", "group-" + k],
})(
series.map((stack, j) => {
const g = (1 - maxWidth) / (maxSeries + 2)
const w = maxWidth / maxSeries
const x = g * (j + 1.5) + w * (j + 0.5)

const tally = map.tally(utils.sum(stack))
const tally = map.tally(sum(stack))

return $.svg({
class: ["series", "series-" + j],
x: utils.percent(x),
width: utils.percent(w),
x: percent(x),
width: percent(w),
})([
...stack.map((d, i) => {
if (!d.value) return

const w = d.width / maxWidth
const h = axes.y.scale(d.value)
const y = axes.y.scale(
axes.y.max - utils.sum(stack.slice(0, i + 1))
)
const y = axes.y.scale(axes.y.max - sum(stack.slice(0, i + 1)))

const rect = $.rect({
x: utils.percent(-w / 2),
y: utils.percent(y),
height: utils.percent(h),
width: utils.percent(w),
x: percent(-w / 2),
y: percent(y),
height: percent(h),
width: percent(w),
fill: d.color[0],
})

const text =
d.label &&
$.text({
y: utils.percent(y + h / 2),
y: percent(y + h / 2),
dy: "0.33em",
color: d.color[1],
})(d.label)
Expand All @@ -240,7 +237,7 @@ export default ({
}),
tally &&
$.text({
y: utils.percent(axes.y.scale(axes.y.max - utils.sum(stack))),
y: percent(axes.y.scale(axes.y.max - sum(stack))),
dy: "-0.5em",
})(tally),
stack[0] &&
Expand Down

0 comments on commit 43e846a

Please sign in to comment.