9f01054a7b66b36077f0bc14e0fcc2d11e833820
3 const th
= require('./template-helper');
5 const optionsDefaults
= {
9 barCaptionFn: () => '',
13 fontFamily: 'DejaVu Sans,Verdana,Geneva,sans-serif',
24 * @param {number} percent grey value
25 * @returns {string} rgb value
27 function grey(percent
) {
28 const value
= Math
.round(95 * (1.0 - percent
));
29 return `rgb(${value}%, ${value}%, ${value}%)`;
34 * @param {object} options options
35 * @param {number} width width
36 * @param {number} height height
37 * @returns {string} svg element
39 function svgHeader(options
, width
, height
) {
44 xmlns="http://www.w3.org/2000/svg"
45 xmlns:xlink="http://www.w3.org/1999/xlink"
46 xmlns:ev="http://www.w3.org/2001/xml-events"
47 width="${width}px" height="${height}px"
48 viewBox="0 0 ${width} ${height}"
49 preserveAspectRatio="xMinYMin meet"`,
50 options
.accessibleText
? ` role="img" aria-label="${th.xmlEscape(options.accessibleText)}"` : '',
52 options
.title
? `<title>${th.xmlEscape(options.title)}</title>` : '',
53 options
.description
? `<desc>${th.xmlEscape(options.description)}</desc>` : '',
60 * @param {options} options options
61 * @param {number} width width
62 * @returns {string} element
64 function svgFrame(options
, width
) {
65 if (!options
.frameColor
) {
68 return `\t<g id="frame">
69 \t\t<rect x="0.5px" y="0.5px" width="${width - 1}px" height="${options.barHeight - 1}px" fill="none" stroke="${options.frameColor}" />
75 * @param {object} options options
76 * @param {number} width width
77 * @returns {string} element
79 function svgTicks(options
, width
) {
80 if (!options
.tickEvery
) {
83 const tickSpacing
= options
.tickEvery
* options
.barWidth
;
84 const tickOffset
= options
.barWidth
/ 2;
85 const numTicks
= Math
.ceil(width
/ tickSpacing
);
86 const ticks
= Array
.from({ length: numTicks
}, (_
, i
) => i
)
87 .map((index
) => `M ${(index * tickSpacing) + tickOffset} ${options.barHeight - 2} v ${options.tickHeight}`)
90 return `\t<g id="ticks">
91 \t<path d="${ticks}" stroke="${options.tickColor}" fill="none" stroke-width="0.5px" />
97 * @param {object} options options
98 * @param {number} width width
99 * @param {number} height height
100 * @returns {string} element
102 function svgLabels(options
, width
, height
) {
104 if (!options
.labelHeight
) {
107 labels
.push(`\t<g font-size="${options.labelHeight}px" font-family="${options.fontFamily}" font-variant="small-caps">\n`);
108 const y
= height
- (options
.labelHeight
/ 2) + 2;
109 if (options
.labelZero
) {
110 labels
.push(`\t\t<text font-size="${options.labelHeight}px" text-anchor="start" x="0" y="${y}">${options.labelZero}</text>\n`);
112 if (options
.labelX
) {
113 labels
.push(`\t\t<text text-anchor="middle" x="${width / 2}" y="${y}">${options.labelX}</text>\n`);
115 labels
.push('\t</g>\n');
120 * @returns {string} element
122 function svgFooter() {
128 * @param {object} options options
129 * @param {number} value value
130 * @param {number} index index
131 * @param {number} maxValue max value
132 * @returns {string} element
134 function svgBar(options
, value
, index
, maxValue
) {
135 const id
= `i${index}`;
136 const x
= options
.barWidth
* index
;
137 const width
= options
.barWidth
;
138 const height
= options
.barHeight
;
139 const scale
= value
/ Math
.max(1, maxValue
);
140 const scaleHeight
= options
.scaleBars
? height
* scale : height
;
141 const yOffset
= height
- scaleHeight
;
142 const fill
= grey(scale
);
143 const emptyFill
= grey(0);
144 const title
= th
.xmlEscape(options
.barCaptionFn(index
, value
));
146 ...(options
.scaleBars
&& [
147 `\t<rect id="${id}" x="${x}" y="0" width="${width}" height="${height}" fill="${emptyFill}">`,
148 ...(title
&& `<title>${title}</title>`),
151 `\t<rect id="${id}" x="${x}" y="${yOffset}" width="${width}" height="${scaleHeight}" fill="${fill}">`,
152 ...(title
&& `<title>${title}</title>`),
157 module
.exports
= (items
, options
= {}) => {
163 const maxValue
= items
.reduce((a
, b
) => Math
.max(a
, b
), 0);
164 const hasLabel
= !!options
.labelX
|| !!options
.labelZero
;
165 const height
= options
.barHeight
+ 2 + (hasLabel
? options
.labelHeight
+ 2 : 0);
166 const width
= Math
.max(items
.length
, options
.minItems
) * options
.barWidth
;
169 ...svgHeader(options
, width
, height
),
170 ...items
.slice(0, options
.maxItems
).map((value
, index
) => svgBar(options
, value
, index
, maxValue
)),
171 svgFrame(options
, width
),
172 svgTicks(options
, width
),
173 ...svgLabels(options
, width
, height
),