New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

shown

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

shown - npm Package Compare versions

Comparing version 1.0.7 to 1.1.0

src/lib/utils/constants.js

16

package.json
{
"name": "shown",
"version": "1.0.7",
"version": "1.1.0",
"description": "Statically-generated, responsive charts, without the need for client-side Javascript.",

@@ -21,6 +21,9 @@ "type": "module",

"badge": "make-coverage-badge --output-path=coverage.svg",
"watch": "nodemon --exec 'npm run build'",
"build": "run-s build:**",
"build:lib": "esbuild src/index.js src/css/shown.css --bundle --platform=node --target=node10.4 --outdir=dist --out-extension:.js=.cjs",
"build:docs": "documentation build ./src/index.js -f html -g -o docs -t ./docs-theme/index.cjs",
"build:types": "jsdoc -p -t node_modules/tsd-jsdoc/dist -r ./src -d types",
"prepublishOnly": "run-s build:**",
"prepublishOnly": "npm run build",
"prerelease": "npm test",
"release": "standard-version"

@@ -62,2 +65,3 @@ },

"make-coverage-badge": "^1.2.0",
"nodemon": "^2.0.22",
"npm-run-all": "^4.1.5",

@@ -90,3 +94,11 @@ "pug": "^3.0.2",

"releaseCommitMessageFormat": "Chore: Release {{currentTag}}"
},
"nodemonConfig": {
"ignore": [
"**/dist/**",
"**/docs/**",
"**/*.ts"
],
"delay": 2500
}
}

@@ -6,2 +6,3 @@ /** @module module:shown */

export { default as line } from "./templates/line.js"
export { default as area } from "./templates/area.js"
export { default as scatter } from "./templates/scatter.js"

@@ -8,0 +9,0 @@ export { default as legend } from "./templates/legend.js"

4

src/lib/color.js

@@ -0,1 +1,3 @@

import { floor } from "./utils/math.js"
// prettier-ignore

@@ -57,3 +59,3 @@ const DEFAULT_COLORS = [

const c = Math.floor(t * (DEFAULT_COLORS.length - 1))
const c = floor(t * (DEFAULT_COLORS.length - 1))

@@ -60,0 +62,0 @@ return t < 0.6 ? [DEFAULT_COLORS[c], "#fff"] : DEFAULT_COLORS[c]

@@ -5,17 +5,19 @@ import monotone from "./curve/monotone.js"

import { stepX, stepY, stepMidX, stepMidY } from "./curve/step.js"
import { isFinite } from "./utils/math.js"
const FIXED = 2
const filter = (p) => Number.isFinite(p[0]) && Number.isFinite(p[1])
const finite = (line) => line.filter(([x, y]) => isFinite(x) && isFinite(y))
const toPath = (path, d) =>
path +
// Add spaces between consecutive numbers (unless negative)
(d >= 0 && isFinite(+path.slice(-1)[0]) ? " " : "") +
// Limit decimals in the path string
(isFinite(d) ? +d.toFixed(FIXED) : d)
const wrap =
(fn) =>
(p, ...args) =>
fn(p.filter(filter), ...args)
.map(
(v, i, a) =>
(Number.isFinite(v) && Number.isFinite(a[i - 1]) ? " " : "") +
(Number.isFinite(v) ? +v.toFixed(FIXED) : v)
)
.join("")
(curve) =>
(line, ...args) =>
curve(finite(line), ...args).reduce(toPath, "")

@@ -22,0 +24,0 @@ export default {

@@ -0,4 +1,6 @@

import { min, abs, clamp } from "../utils/math.js"
const TENSION = 1 / 3
const sign = (x) => (x < 0 ? -1 : 1)
const sign = (v) => (v < 0 ? -1 : 1)

@@ -12,12 +14,11 @@ const slope = (x0, y0, x1, y1, x2, y2) => {

return (
(sign(s0) + sign(s1)) *
Math.min(Math.abs(s0), Math.abs(s1), Math.abs(p) / 2) || 0
)
return (sign(s0) + sign(s1)) * min(abs(s0), abs(s1), abs(p) / 2) || 0
}
const curve = (x0, y0, x1, y1, s0, s1, alpha) => {
const dx0 = (x1 - x0) * alpha
const dx1 = (x1 - x0) * alpha
return ["C", x0 + dx0, y0 + dx0 * s0, x1 - dx1, y1 - dx1 * s1, x1, y1]
const ratio = abs((y1 - y0) / (x1 - x0))
const scale = clamp(ratio, 1, 1.5)
const delta = (x1 - x0) * alpha * scale
return ["C", x0 + delta, y0 + delta * s0, x1 - delta, y1 - delta * s1, x1, y1]
}

@@ -24,0 +25,0 @@

@@ -8,4 +8,7 @@ export const stepX = (points) =>

const dy = p1[1] - p0[1]
if (dx === 0 && dy === 0) return m
return m.concat(["h", dx, "v", dy])
if (dx !== 0) m.push("h", dx)
if (dy !== 0) m.push("v", dy)
return m
}

@@ -21,4 +24,7 @@ }, [])

const dy = p1[1] - p0[1]
if (dx === 0 && dy === 0) return m
return m.concat(["v", dy, "h", dx])
if (dy !== 0) m.push("v", dy)
if (dx !== 0) m.push("h", dx)
return m
}

@@ -32,6 +38,10 @@ }, [])

const p0 = points[i - 1]
const dx = p1[0] - p0[0]
const dx = (p1[0] - p0[0]) / 2
const dy = p1[1] - p0[1]
if (dx === 0 && dy === 0) return m
return m.concat(["h", dx / 2, "v", dy, "h", dx / 2])
if (dx !== 0) m.push("h", dx)
if (dy !== 0) m.push("v", dy)
if (dx !== 0) m.push("h", dx)
return m
}

@@ -46,6 +56,10 @@ }, [])

const dx = p1[0] - p0[0]
const dy = p1[1] - p0[1]
if (dx === 0 && dy === 0) return m
return m.concat(["v", dy / 2, "h", dx, "v", dy / 2])
const dy = (p1[1] - p0[1]) / 2
if (dy !== 0) m.push("v", dy)
if (dx !== 0) m.push("h", dx)
if (dy !== 0) m.push("v", dy)
return m
}
}, [])
import { get as getColor, wrap as wrapColor } from "./color.js"
import utils from "./utils.js"
import decimalPlaces from "./utils/decimal-places.js"
import { min, max, isFinite } from "./utils/math.js"

@@ -37,3 +38,3 @@ /**

* The default function returns the _index_ of the item.
* **Line and Scatter Chart only**
* **Line, Area and Scatter Chart only**
* @property {Function|number[]|number} [y]

@@ -43,3 +44,3 @@ * Parse the y-axis value from the data. This function is useful if your

* The default function returns the _value_ of the item.
* **Line and Scatter Chart only**
* **Line, Area and Scatter Chart only**
* @property {Function|number[]|number} [r]

@@ -84,3 +85,4 @@ * Parse the radial size from the data. This function is useful if you want to

* function is useful if you want to override or add arbitrary attributes on the
* chart.
* chart. For example, you could add a `data-tooltip` attribute to trigger
* tooltips using a JavaScript library.
*/

@@ -133,3 +135,3 @@

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

@@ -140,8 +142,8 @@ // By default, a label will only show when it exceeds the minimum value

if (map.label === undefined || map.label === true) {
const max = Math.max(...values)
const maxValue = max(...values)
map.label = (v) =>
(v = map.value(v)) &&
Number.isFinite(v) &&
v / max >= minValue &&
isFinite(v) &&
v / maxValue >= minValue &&
v.toFixed(places)

@@ -159,4 +161,3 @@ }

if (map.tally === true) {
map.tally = (v) =>
(v = map.value(v)) && Number.isFinite(v) && v.toFixed(places)
map.tally = (v) => (v = map.value(v)) && isFinite(v) && v.toFixed(places)
}

@@ -163,0 +164,0 @@

import $ from "../lib/dom/index.js"
import utils from "../lib/utils.js"
// The number of ticks to use, in preferential order.
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"
import {
isFinite,
isInteger,
floor,
ceil,
abs,
min,
max,
pow,
} from "../lib/utils/math.js"
// The number of ticks to use, in preferential order
const TICKCOUNT_ORDER = [5, 6, 7, 8, 4, 9, 3, 10, 11, 12, 13]

@@ -51,30 +65,25 @@ const MAX_PRECISION = 7

* @private
* @param {number} min
* @param {number} max
* @param {number} vmin
* @param {number} vmax
* @returns {number} scale
*/
const getScale = (min, max) => {
let m = Math.max(utils.magnitude(min), utils.magnitude(max))
const getScale = (vmin, vmax) => {
let m = max(magnitude(vmin), magnitude(vmax))
// The magnitude should be no greater than the difference
m = Math.min(m, utils.magnitude(max - min))
m = min(m, magnitude(vmax - vmin))
// Deal with integers from here
let scale = Math.pow(10, m)
let scale = pow(10, m)
// If both values are integers and min is positive,
// all ticks should only occur at integer positions
if (
Number.isInteger(max) &&
Number.isInteger(min) &&
min > 0 &&
scale === 1
) {
if (isInteger(vmax) && isInteger(vmin) && vmin > 0 && scale === 1) {
return 1
}
min /= scale
max /= scale
vmin /= scale
vmax /= scale
let d = max - min
let d = vmax - vmin

@@ -94,3 +103,3 @@ // Increase the scale when the difference is too small

LOW_PRIMES.forEach((p) => {
if (min > 0 || max < 0 ? d % p === 0 : min % p === 0 && max % p === 0) {
if (vmin > 0 || vmax < 0 ? d % p === 0 : vmin % p === 0 && vmax % p === 0) {
scale /= p

@@ -113,20 +122,20 @@ }

let min = utils.toPrecision(Math.min(...values), MAX_PRECISION)
let max = utils.toPrecision(Math.max(...values), MAX_PRECISION)
let vmin = toPrecision(min(...values), MAX_PRECISION)
let vmax = toPrecision(max(...values), MAX_PRECISION)
if (min === max) {
if (vmin === vmax) {
// All values are zero
if (max === 0) return [0, 1]
if (vmax === 0) return [0, 1]
// The bounds should be between zero and max
if (min < 0) max = 0
else min = 0
if (vmin < 0) vmax = 0
else vmin = 0
}
const f = getScale(min, max)
const f = getScale(vmin, vmax)
min = Math.floor(min / f)
max = Math.ceil(max / f)
vmin = floor(vmin / f)
vmax = ceil(vmax / f)
const d = max - min
const d = vmax - vmin

@@ -137,6 +146,6 @@ // The distance should be divisible by a low prime number

if (d % 2 > 0 && !LOW_PRIMES.some((p) => d % p === 0)) {
if (Math.abs(max) % 2 === 1) {
max += 1
if (abs(vmax) % 2 === 1) {
vmax += 1
} else {
min -= 1
vmin -= 1
}

@@ -146,4 +155,4 @@ }

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

@@ -157,20 +166,20 @@ }

* @private
* @param {number} min
* @param {number} max
* @param {number} vmin
* @param {number} vmax
* @returns {number} count
*/
const getTicks = (min, max) => {
if (!Number.isFinite(min) || !Number.isFinite(max)) return 0
if (min === max) return 0
const getTicks = (vmin, vmax) => {
if (!isFinite(vmin) || !isFinite(vmax)) return 0
if (vmin === vmax) return 0
const f = getScale(min, max)
const f = getScale(vmin, vmax)
min = utils.toPrecision(min / f, MAX_PRECISION)
max = utils.toPrecision(max / f, MAX_PRECISION)
vmin = toPrecision(vmin / f, MAX_PRECISION)
vmax = toPrecision(vmax / f, MAX_PRECISION)
let d = max - min
let d = vmax - vmin
if (min >= 0 || max <= 0) {
if (vmin >= 0 || vmax <= 0) {
LOW_PRIMES.forEach((p) => {
if (d > Math.max(10, 10 * f) && d % p === 0) {
if (d > max(10, 10 * f) && d % p === 0) {
d /= p

@@ -181,12 +190,12 @@ }

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 &&
(min > 0 || max < 0 || (min % mod === 0 && max % mod === 0))
decimalPlaces(dec) <= maxDecimals &&
(vmin > 0 || vmax < 0 || (vmin % mod === 0 && vmax % mod === 0))
)

@@ -205,32 +214,31 @@ }) || 2

export const setup = (axis = {}, data, guessBounds = true) => {
let { min, max, ticks, label, line, spine, inset } = axis
let _min, _max, hasOverflow, width
let { min: vmin, max: vmax, ticks, label, line, spine, inset } = axis
let bmin, bmax, hasOverflow, width
const showLabel =
!!data || Number.isFinite(axis.min) || Number.isFinite(axis.max)
const showLabel = !!data || isFinite(axis.min) || isFinite(axis.max)
if (data) {
if (guessBounds) {
const values = [min, max, ...data].filter(Number.isFinite)
const values = [vmin, vmax, ...data].filter(isFinite)
// Unless every number is an integer, assume zero should
// appear on either end of the scale
if (values.some((n) => !Number.isInteger(n))) {
if (values.some((n) => !isInteger(n))) {
values.push(0)
}
;[_min, _max] = getBounds(values)
;[bmin, bmax] = getBounds(values)
} else {
_min = Math.min(...data.filter(Number.isFinite))
_max = Math.max(...data.filter(Number.isFinite))
bmin = min(...data.filter(isFinite))
bmax = max(...data.filter(isFinite))
}
} else {
_min = 0
_max = ticks - 1
bmin = 0
bmax = ticks - 1
}
hasOverflow = _min < min || _max > max
min = min ?? _min
max = max ?? _max
ticks = ticks ?? getTicks(min, max)
hasOverflow = bmin < vmin || bmax > vmax
vmin = vmin ?? bmin
vmax = vmax ?? bmax
ticks = ticks ?? getTicks(vmin, vmax)
inset = inset ?? 0

@@ -249,8 +257,8 @@

// only every second tick will be labeled.
const length = Math.max(Math.abs(max), Math.abs(min)).toString().length
const length = max(abs(vmax), abs(vmin)).toString().length
label = (v, i) =>
(ticks < 8 || i % 2 === 0) &&
Math.abs(v).toString().length <= length &&
utils.toPrecision(v, MAX_PRECISION)
abs(v).toString().length <= length &&
toPrecision(v, MAX_PRECISION)
}

@@ -274,3 +282,3 @@

if (max === min) {
if (vmax === vmin) {
grid = [0.5]

@@ -280,10 +288,11 @@ inset = 0.5

const scale = (v) => (max === min ? 0.5 : pad((v - min) / (max - min), inset))
const scale = (v) =>
vmax === vmin ? 0.5 : pad((v - vmin) / (vmax - vmin), inset)
if (label) {
width = Math.max(
width = max(
...grid
.map((t) => min + t * (max - min))
.map((t) => vmin + t * (vmax - vmin))
.map(label)
.filter((t) => t.length || Number.isFinite(t))
.filter((t) => t.length || isFinite(t))
.map((t) => t.toString().length)

@@ -295,6 +304,6 @@ )

...axis,
min: vmin,
max: vmax,
grid,
ticks,
min,
max,
label,

@@ -319,11 +328,28 @@ line,

const lineProps = type === "x" ? { y2: "100%" } : { x2: "100%" }
const line = (t, className, d) => {
const props = { class: className }
const v = t !== 0 && percent(t)
if (type === "x") {
props.x1 = v
props.x2 = v
props.y2 = percent(1)
} else {
props.y1 = v
props.y2 = v
props.x2 = percent(1)
}
if (d) {
const offset = (type === "x" ? [d, 0] : [0, -d]).join(" ")
props.transform = `translate(${offset})`
}
return $.line(props)
}
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 = []

@@ -342,30 +368,6 @@

const altLProps =
type === "x"
? {
x1: utils.percent(-altOffset),
x2: utils.percent(-altOffset),
}
: {
y1: utils.percent(-altOffset),
y2: utils.percent(-altOffset),
}
const altRProps =
type === "x"
? { x1: utils.percent(altOffset), x2: utils.percent(altOffset) }
: { y1: utils.percent(altOffset), y2: utils.percent(altOffset) }
lines.push(
(axis.inset || i > 0) &&
$.line({
class: "axis-line",
...lineProps,
...altLProps,
}),
i === axis.grid.length - 1 &&
$.line({
class: "axis-line",
...lineProps,
...altRProps,
})
(axis.inset || t > 0) &&
line(-altOffset, "axis-line", t === 0 && 0.5),
t === 1 && line(altOffset, "axis-line", -0.5)
)

@@ -375,6 +377,7 @@ }

lines.push(
$.line({
class: v == 0 ? "axis-base" : "axis-line",
...lineProps,
})
line(
0,
v == 0 ? "axis-base" : "axis-line",
t === 0 ? 0.5 : t === 1 ? -0.5 : 0
)
)

@@ -386,4 +389,4 @@ }

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)

@@ -393,11 +396,5 @@ })

if (axis.spine) {
const spineProps = { class: "axis-spine", ...lineProps }
// Add an initial line if the first line is inset
if (!axis.line(axis.min, 0, axis) || pad(axis.grid[0], axis.inset) > 0) {
if (type === "x") {
children.unshift($.line(spineProps))
} else {
children.push($.line({ y1: "100%", y2: "100%", ...spineProps }))
}
children.unshift(line(0, "axis-spine", 0.5))
}

@@ -410,7 +407,3 @@

) {
if (type === "x") {
children.push($.line({ x1: "100%", x2: "100%", ...spineProps }))
} else {
children.unshift($.line(spineProps))
}
children.push(line(1, "axis-spine", -0.5))
}

@@ -417,0 +410,0 @@ }

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"
import { max } from "../lib/utils/math.js"
import wrap from "./wrap.js"

@@ -116,4 +118,4 @@

const maxStack = Math.max(...data.flat(1).map((d) => d.length))
const maxSeries = Math.max(...data.map((d) => d.length))
const maxStack = max(...data.flat(1).map((d) => d.length))
const maxSeries = max(...data.map((d) => d.length))

@@ -158,3 +160,3 @@ // For unstacked charts, tally results rather than label.

},
stack ? data.flat().map((d) => utils.sum(d)) : data.flat(2),
stack ? data.flat().map((d) => sum(d)) : data.flat(2),
{ minValue: 0.05 }

@@ -165,4 +167,4 @@ )

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

@@ -196,6 +198,4 @@ xAxis = {

$.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],

@@ -208,8 +208,8 @@ })(

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),
})([

@@ -221,11 +221,9 @@ ...stack.map((d, i) => {

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],

@@ -237,3 +235,3 @@ })

$.text({
y: utils.percent(y + h / 2),
y: percent(y + h / 2),
dy: "0.33em",

@@ -250,3 +248,3 @@ color: d.color[1],

$.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",

@@ -253,0 +251,0 @@ })(tally),

@@ -16,3 +16,3 @@ import $ from "../lib/dom/index.js"

if (type) {
if (type && type !== "none") {
symbol = [

@@ -19,0 +19,0 @@ includeLine &&

import $ from "../lib/dom/index.js"
import utils from "../lib/utils.js"
import percent from "../lib/utils/percent.js"
import sum from "../lib/utils/sum.js"
import interpolate from "../lib/utils/interpolate.js"
import { isFinite } from "../lib/utils/math.js"
import curve from "../lib/curve.js"

@@ -38,14 +41,14 @@ import Map from "../lib/map.js"

* @param {number[]} points
* @param {function(Point, number): boolean} toPoint
* @param {boolean} skip
* @returns {number} ticks
* @param {Axis} xAxis
* @param {Axis} yAxis
* @param {boolean} showGaps
* @returns {string} path
*/
const linePath = (points, toPoint, skip) =>
points
.reduce((m, d, i) => {
const linePath = (points, xAxis, yAxis, showGaps) => {
let path = points
.reduce((m, d) => {
const l = m[m.length - 1]
const p = toPoint(d, i)
if (!p) {
if (skip) {
if (!(isFinite(d.x) && isFinite(d.y))) {
if (showGaps) {
return [...m, { curve: d.curve, points: [] }]

@@ -57,2 +60,7 @@ } else {

const p = [
SVGLINE_VIEWPORT_W * xAxis.scale(d.x),
SVGLINE_VIEWPORT_H * (1 - yAxis.scale(d.y)),
]
if (l) l.points.push(p)

@@ -68,14 +76,60 @@

let args = []
let path
let type = l.curve
// Some curve types allow other parameters to be passed to the curve
// function. For example, setting the tension on "monotone" or "bump".
if (Array.isArray(l.curve)) {
;[l.curve, ...args] = l.curve
if (Array.isArray(type)) {
;[type, ...args] = type
}
return curve[l.curve](l.points, ...args)
path = curve[type](l.points, ...args)
return path
})
.join("")
return path
}
/**
* An area path is constructed using the linePath of the current and next line,
* or the baseline when it is the final line.
* @private
* @param {number[]} line1
* @param {number[]} line2
* @param {Axis} xAxis
* @param {Axis} yAxis
* @returns {string} path
*/
const areaPath = (line1, line2, xAxis, yAxis) => {
const path1 = linePath(line1, xAxis, yAxis, false)
// The first path reverses along the baseline
if (!line2) {
return (
path1 +
`L${SVGLINE_VIEWPORT_W},${SVGLINE_VIEWPORT_H}L0,${SVGLINE_VIEWPORT_H}Z`
)
}
// Some curves are asymmetric and need to be mirrored
const mirror = {
stepX: "stepY",
stepY: "stepX",
}
line2 = line2
.map((d) => ({
...d,
curve: mirror[d.curve] || d.curve,
}))
.reverse()
const path2 = linePath(line2, xAxis, yAxis, false)
return path1 + "L" + path2.slice(1) + "Z"
}
/**
* Generate a line chart.

@@ -99,3 +153,5 @@ * @alias module:shown.line

* Points in the line with non-finite values are rendered as broken lines
* where data is unavailable. Set to `false` to ignore missing values instead.
* where data is unavailable. Set to `false` to skip missing values instead.
* @param {Boolean} [options.area] Render the line chart as an area chart.
* @param {boolean} [options.sorted] - Whether to sort the values.
* @returns {string} Rendered chart

@@ -146,3 +202,2 @@ *

*/
export default ({

@@ -156,2 +211,4 @@ data,

yAxis,
area = false,
sorted = false,
}) => {

@@ -180,2 +237,40 @@ data = Array.isArray(data[0]) ? data : [data]

if (sorted) {
data.sort((al, bl) => {
const a = sum(al)
const b = sum(bl)
return a === b ? 0 : a > b ? 1 : -1
})
}
if (area) {
// For lines with fewer points, continue along the baseline
data.forEach((line, i) => {
const curve = line.at(-1)?.curve
while (line.length < maxLength) {
line.push({ x: map.x({}, i, line.length), y: 0, ignore: true, curve })
}
})
// If a line is discontinuous at a point where the previous is not,
// interpolate an average value for that point
data.slice(1).forEach((next, j) => {
const prev = data[j]
const prevBase = interpolate(prev.map((d) => d.y))
const nextBase = interpolate(next.map((d) => d.y))
next.forEach((item, i) => {
if (isFinite(prev[i]?.y) && !isFinite(item.y)) {
item.y = nextBase[i]
}
if (isFinite(item.y)) {
item.y += prevBase[i]
}
})
})
}
// prettier-ignore

@@ -189,6 +284,28 @@ const axes = {

const axisY = axisTemplate("y", axes.y)
const viewBox = `0 0 ${SVGLINE_VIEWPORT_W} ${SVGLINE_VIEWPORT_H}`
const areas =
area &&
$.svg({
class: "areas",
viewBox,
preserveAspectRatio: "none",
style: (axes.x.hasOverflow || axes.y.hasOverflow) && "overflow: hidden;",
})(
data
.filter((line) => line.length > 0)
.reverse()
.map((line, i, arr) =>
$.path({
"class": ["series", "series-" + i],
"vector-effect": "non-scaling-stroke",
"fill": line[0].color[0],
"d": areaPath(line, arr[i + 1], axes.x, axes.y),
})
)
)
const lines = $.svg({
class: "lines",
viewBox: `0 0 ${SVGLINE_VIEWPORT_W} ${SVGLINE_VIEWPORT_H}`,
viewBox,
preserveAspectRatio: "none",

@@ -205,12 +322,3 @@ style: (axes.x.hasOverflow || axes.y.hasOverflow) && "overflow: hidden;",

"fill": "none",
"d": linePath(
line,
(d) =>
Number.isFinite(d.x) &&
Number.isFinite(d.y) && [
SVGLINE_VIEWPORT_W * axes.x.scale(d.x),
SVGLINE_VIEWPORT_H * (1 - axes.y.scale(d.y)),
],
showGaps
),
"d": linePath(line, axes.x, axes.y, showGaps),
})

@@ -227,14 +335,13 @@ )

$.svg({
"class": ["series", "series-" + j],
"text-anchor": "middle",
"color": data[0]?.color[0],
class: ["series", "series-" + j],
color: data[0]?.color[0],
})(
data.map((d) => {
return (
Number.isFinite(d.x) &&
Number.isFinite(d.y) &&
isFinite(d.x) &&
isFinite(d.y) &&
d.shape &&
$.use({
x: utils.percent(axes.x.scale(d.x)),
y: utils.percent(1 - axes.y.scale(d.y)),
x: percent(axes.x.scale(d.x)),
y: percent(1 - axes.y.scale(d.y)),
href: `#symbol-${d.shape}`,

@@ -259,3 +366,3 @@ width: "1em",

"chart",
"chart-line",
area ? "chart-area" : "chart-line",
axes.x.label && "has-xaxis xaxis-w" + axes.x.width,

@@ -282,2 +389,3 @@ axes.y.label && "has-yaxis yaxis-w" + axes.y.width,

axisX,
areas,
lines,

@@ -284,0 +392,0 @@ symbols,

import $ from "../lib/dom/index.js"
import utils from "../lib/utils.js"
import percent from "../lib/utils/percent.js"
import sum from "../lib/utils/sum.js"
import toPrecision from "../lib/utils/to-precision.js"
import Map from "../lib/map.js"
import wrap from "./wrap.js"
import legendTemplate from "./legend.js"
import { min, max, tau, cos, sin } from "../lib/utils/math.js"
const tau = Math.PI * 2
const arc = (t, r) => percent((t % 1) * tau * r)
const arc = (t, r) => utils.percent((t % 1) * tau * r)
/**

@@ -27,9 +28,9 @@ * Calculate the bounds based on the portion of the circle

const xs = ts.map((t) => utils.toPrecision(Math.cos(t * tau) / 2))
const ys = ts.map((t) => utils.toPrecision(Math.sin(t * tau) / 2))
const xs = ts.map((t) => toPrecision(cos(t * tau) / 2))
const ys = ts.map((t) => toPrecision(sin(t * tau) / 2))
const maxX = Math.max(...xs)
const minX = Math.min(...xs)
const maxY = Math.max(...ys)
const minY = Math.min(...ys)
const maxX = max(...xs)
const minX = min(...xs)
const maxY = max(...ys)
const minY = min(...ys)

@@ -111,3 +112,3 @@ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY }

const total = utils.sum(data)
const total = sum(data)
const scale = endAngle - startAngle

@@ -117,3 +118,3 @@

const t = (d.value / total) * scale
const o = startAngle + (utils.sum(data.slice(0, i)) / total) * scale
const o = startAngle + (sum(data.slice(0, i)) / total) * scale

@@ -127,8 +128,8 @@ const radius = (1 - d.width / 2) / 2

const x = Math.cos(theta) * shift * radius
const y = Math.sin(theta) * shift * radius
const x = cos(theta) * shift * radius
const y = sin(theta) * shift * radius
return $.g({
"class": `segment segment-${i}`,
"aria-label": `${d.label} (${utils.percent(t)})`,
"aria-label": `${d.label} (${percent(t)})`,
"attrs": d.attrs,

@@ -142,7 +143,7 @@ })([

"role": "presentation",
"r": utils.percent(radius),
"r": percent(radius),
"stroke": d.color[0],
"stroke-dasharray": dasharray,
"stroke-dashoffset": dashoffset,
"stroke-width": utils.percent(d.width / 2),
"stroke-width": percent(d.width / 2),
"fill": "none",

@@ -154,4 +155,4 @@ })

class: "segment-label",
x: utils.percent(x),
y: utils.percent(y),
x: percent(x),
y: percent(y),
dy: "0.33em",

@@ -183,6 +184,6 @@ role: "presentation",

$.svg({
"x": utils.percent(0.5 - (bounds.x + bounds.w / 2) / bounds.w),
"y": utils.percent(0.5 - (bounds.y + bounds.h / 2) / bounds.h),
"width": utils.percent(1 / bounds.w),
"height": utils.percent(1 / bounds.h),
"x": percent(0.5 - (bounds.x + bounds.w / 2) / bounds.w),
"y": percent(0.5 - (bounds.y + bounds.h / 2) / bounds.h),
"width": percent(1 / bounds.w),
"height": percent(1 / bounds.h),
"role": "presentation",

@@ -189,0 +190,0 @@ "text-anchor": "middle",

import $ from "../lib/dom/index.js"
import utils from "../lib/utils.js"
import percent from "../lib/utils/percent.js"
import Map from "../lib/map.js"

@@ -102,4 +102,4 @@ import legendTemplate from "./legend.js"

$.use({
x: utils.percent(axes.x.scale(d.x)),
y: utils.percent(1 - axes.y.scale(d.y)),
x: percent(axes.x.scale(d.x)),
y: percent(1 - axes.y.scale(d.y)),
href: `#symbol-${d.shape}`,

@@ -106,0 +106,0 @@ width: `${d.r}em`,

@@ -26,3 +26,3 @@ import $ from "../lib/dom/index.js"

symbol = $.path({
d: "M0-5L5,0L0,5L-5,0Z",
d: "M0-5.5L5.5,0L0,5.5L-5.5,0Z",
})

@@ -29,0 +29,0 @@ }

declare module "shown" {
/**
* Generate an area chart.
* @example
* shown.area({
* title: "Stacked area chart",
* data: [
* [52.86, 20.65, 14.54, 10.09, 8.41],
* [21.97, 31.71, 6.31, 17.85, 23.53],
* [ 6.73, 10.84, 37.62, 45.79, 53.32],
* [38.44, 50.79, 22.31, 31.82, 7.64],
* ],
* map: {
* curve: "monotone",
* key: ["α", "β", "γ", "δ"],
* },
* sorted: true,
* })
* @example
* shown.area({
* title: "Discontinuous data is interpolated",
* data: [
* [ 12.2, 19.2, 35.9, 88.1, 12.8, 48.2, ],
*  [ 25.7, 10.1, 48.5, 84.4, 39.6, ],
* [ 11.0, 43.5, 68.4, 79.6, null, null, 35.4 ],
* [ 20.3, null, 17.5, 71.6, 67.1, 64.1, 25.4 ],
* ],
* map: {
* key: ["A", "B", "C", "D"],
* },
* })
* @param options - Data and display options for the chart. Area charts
* are a wrapper for line charts, with the default options for `sorted` and
* `area` set to true.
* @param options.data - The data for this chart. Data can
* be passed either as a flat array for a single line, or nested arrays
* for multiple lines.
* @param [options.title] - The title for this chart, set to the
* `<title>` element for better accessibility.
* @param [options.description] - The description for this chart, set
* to the `<desc>` element for better accessibility.
* @param [options.map] - Controls for transforming data. See {@link MapOptions} for more details.
* @param [options.xAxis] - Overrides for the x-axis. See {@link AxisOptions} for more details.
* @param [options.yAxis] - Overrides for the y-axis. See {@link AxisOptions} for more details.
* @param [options.sorted] - Whether to sort the values.
* @returns Rendered chart
*/
function area(options: {
data: any[];
title?: string;
description?: string;
map?: MapOptions;
xAxis?: AxisOptions;
yAxis?: AxisOptions;
sorted?: boolean;
}): string;
/**
* Generate a bar chart.

@@ -171,3 +226,5 @@ * @example

* @param [options.showGaps] - Points in the line with non-finite values are rendered as broken lines
* where data is unavailable. Set to `false` to ignore missing values instead.
* where data is unavailable. Set to `false` to skip missing values instead.
* @param [options.area] - Render the line chart as an area chart.
* @param [options.sorted] - Whether to sort the values.
* @returns Rendered chart

@@ -183,2 +240,4 @@ */

showGaps?: boolean;
area?: boolean;
sorted?: boolean;
}): string;

@@ -320,7 +379,7 @@ /**

* The default function returns the _index_ of the item.
* **Line and Scatter Chart only**
* **Line, Area and Scatter Chart only**
* @property [y] - Parse the y-axis value from the data. This function is useful if your
* data structure wraps each value in an object.
* The default function returns the _value_ of the item.
* **Line and Scatter Chart only**
* **Line, Area and Scatter Chart only**
* @property [r] - Parse the radial size from the data. This function is useful if you want to

@@ -355,3 +414,4 @@ * visualise another dimension in the data. If the radius is not greater

* function is useful if you want to override or add arbitrary attributes on the
* chart.
* chart. For example, you could add a `data-tooltip` attribute to trigger
* tooltips using a JavaScript library.
*/

@@ -358,0 +418,0 @@ declare type MapOptions = {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc