From f5c05d27a8fe01a86c8ab3678e37c2774d1fd79f Mon Sep 17 00:00:00 2001 From: Tal Hayut Date: Tue, 4 Jun 2024 10:14:02 +0300 Subject: [PATCH] Chartscii 3.1 (#140) * feat: add value labels * fix: upgrade styl3 * fix: readme * feat: final touches for valueLabels * chore: upgrading styl3 * fix: snapshots * docs: updated readme for valueLabels * fix: refactors --- README.md | 88 ++- branding/shellfie.js | 70 ++ branding/test.ts | 39 + formatters/formatter.ts | 45 -- formatters/horizontal.ts | 26 +- formatters/vertical.ts | 55 +- index.test.ts | 89 ++- options/options.ts | 1 + package-lock.json | 7 +- package.json | 2 +- processor/processor.ts | 53 +- shellfie.js | 56 -- shellfies/chartscii_chartpoint.png | Bin 39375 -> 54034 bytes snapshots/index.test.json | 1144 +++++++++++++++------------- test.ts | 17 - tsconfig.json | 2 +- types/types.ts | 5 +- 17 files changed, 951 insertions(+), 748 deletions(-) create mode 100644 branding/shellfie.js create mode 100644 branding/test.ts delete mode 100644 shellfie.js delete mode 100644 test.ts diff --git a/README.md b/README.md index 352b971..061fa9b 100644 --- a/README.md +++ b/README.md @@ -85,14 +85,18 @@ For maximum flexibility, provide an array of chart points. This will allow you t ### example ```tsx -import Chartscii from 'chartscii'; +import Chartscii from "chartscii"; -const data = [{ label: 'label', value: 2, color: 'pink' }, { label: 'label', value: 2, color: 'purple' }, { label: 'label', value: 2, color: 'marine' },]; +const data = [ + { label: "label", value: 2, color: "pink" }, + { label: "label", value: 2, color: "purple" }, + { label: "label", value: 2, color: "marine" } +]; const chart = new Chartscii(data, { colorLabels: true }); console.log(chart.create()); ``` -![](./shellfies/chartscii_chartpoint.png) +![](./shellfies/chartscii_chartpoint.png) # Options @@ -132,26 +136,27 @@ const options: ChartOptions = { ## customization options -| name | description | type | default | -| ----------- | ---------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------ | -| percentage | calculate and show percentage data | `boolean` | `false` | -| colorLabels | color labels with provided color per label, or color provided to option | `boolean` | `false` | -| sort | sort the input data | `boolean` | `false` | -| reverse | reverse the input data | `boolean` | `false` | -| naked | don’t print chart structure ascii characters | `boolean` | `false` | -| labels | show labels | `boolean` | `true` | -| color | fallback color or unified char bars color | `string` | `undefined` | -| fill | use this character to fill remaining chart bar space | `string` | `undefined` | -| maxValue | values are scaled proportionate to this value. otherwise the max will be calculated from the provided data. | `number` | `undefined` | -| width | width of chart | `number` | `100` | -| height | height of chart | `number` | `10` | -| padding | padding between bars | `number` | `0` | -| barSize | size of each bar | `number` | `1` | -| title | chart title | `string` | `undefined` | -| char | use this character to draw the chart bars | `string` | `█` | -| orientation | horizontal or vertical | `string` | `horizontal` | -| theme | `styl3`'s [themes](https://github.com/tool3/styl3?tab=readme-ov-file#themes) | `string` | `undefined` | -| structure | use these characters to draw the enclosing chart borders. | `object` | `{ x: '═', y: '╢', bottomLeft: '╚', axis: '║', topLeft: '╔' }` | +| name | description | type | default | +| ----------- | ----------------------------------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------- | +| percentage | calculate and show percentage data | `boolean` | `false` | +| colorLabels | color labels with provided color per label, or color provided to option | `boolean` | `false` | +| valueLabels | show values of each bar | `boolean` | `true` | +| sort | sort the input data | `boolean` | `false` | +| reverse | reverse the input data | `boolean` | `false` | +| naked | don’t print chart structure ascii characters | `boolean` | `false` | +| labels | show labels | `boolean` | `true` | +| color | fallback color or unified char bars color | `string` | `undefined` | +| fill | use this character to fill remaining chart bar space | `string` | `undefined` | +| scale | values are scaled proportionate to this value. otherwise the max will be calculated from the provided data. | `number` or `string` | `auto` | +| width | width of chart | `number` | `100` | +| height | height of chart | `number` | `10` | +| padding | padding between bars | `number` | `0` | +| barSize | size of each bar | `number` | `1` | +| title | chart title | `string` | `undefined` | +| char | use this character to draw the chart bars | `string` | `█` | +| orientation | horizontal or vertical | `string` | `horizontal` | +| theme | `styl3`'s [themes](https://github.com/tool3/styl3?tab=readme-ov-file#themes) | `string` | `undefined` | +| structure | use these characters to draw the enclosing chart borders. | `object` | `{ x: '═', y: '╢', bottomLeft: '╚', axis: '║', topLeft: '╔' }` | ## chartscii + styl3 = ❤️ @@ -187,36 +192,37 @@ const chart = new Chartscii(data, { console.log(chart.create()); ``` -![](./shellfies/chartscii_styl3.png) +![](./shellfies/chartscii_styl3.png) # examples -here are some examples of charts using `styl3`'s formatting on the chart labels. + +here are some examples of charts using `styl3`'s formatting on the chart labels. > [!TIP] -> you can run more examples from the `./examples/` directory of this repository using `ts-node`. -> for example `npx ts-node examples/loaders.ts` +> you can run more examples from the `./examples/` directory of this repository using `ts-node`. +> for example `npx ts-node examples/loaders.ts` ## vertical -| options | chart | -| ---- | ----- | -| beach theme with italic and bold labels with a bar size of 2 | ![](./shellfies/vertical/chartscii_beach_italic_bold_barsize.png) | -| pastel theme with bold and underlined labels with padding of 2 | ![](./shellfies/vertical/chartscii_pastel_bold_underline_padding.png) | -| lush theme with strikedout labels no padding and emoji | ![](./shellfies/vertical/chartscii_lush_strikeout_emoji.png) | -| lush theme with underlined labels no padding and no axis structure char | ![](./shellfies/vertical/chartscii_lush_underline_no_axis_structure.png) | -| standard theme with dimmed and italic labels and padding 1 | ![](./shellfies/vertical/chartscii_standard_dimmed_italic_padding_structure.png) | -| pastel theme with inverted and underlined labels with a dark fill character | ![](./shellfies/vertical/chartscii_pastel_inverted_underline_dark_fill.png) | +| options | chart | +| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| beach theme with italic and bold labels with a bar size of 2 | ![](./shellfies/vertical/chartscii_beach_italic_bold_barsize.png) | +| pastel theme with bold and underlined labels with padding of 2 | ![](./shellfies/vertical/chartscii_pastel_bold_underline_padding.png) | +| lush theme with strikedout labels no padding and emoji | ![](./shellfies/vertical/chartscii_lush_strikeout_emoji.png) | +| lush theme with underlined labels no padding and no axis structure char | ![](./shellfies/vertical/chartscii_lush_underline_no_axis_structure.png) | +| standard theme with dimmed and italic labels and padding 1 | ![](./shellfies/vertical/chartscii_standard_dimmed_italic_padding_structure.png) | +| pastel theme with inverted and underlined labels with a dark fill character | ![](./shellfies/vertical/chartscii_pastel_inverted_underline_dark_fill.png) | ## horizontal -| options | chart | -| ---- | ----- | -| pastel theme with bold labels and percentage | ![](./shellfies/horizontal/chartscii_pastel_bold_percentage.png) | -| lush theme with inverted labels and naked chart | ![](./shellfies/horizontal/chartscii_pastel_lush_invert_naked.png) | +| options | chart | +| --------------------------------------------------------------------- | ------------------------------------------------------------------- | +| pastel theme with bold labels and percentage | ![](./shellfies/horizontal/chartscii_pastel_bold_percentage.png) | +| lush theme with inverted labels and naked chart | ![](./shellfies/horizontal/chartscii_pastel_lush_invert_naked.png) | | beach theme with underlined labels and different structure characters | ![](./shellfies/horizontal/chartscii_beach_underline_structure.png) | -| pastel theme with padding of 1 and custom char | ![](./shellfies/horizontal/chartscii_pastel_char.png) | -| pastel theme with naked chart - can be used to create loaders | ![](./shellfies/horizontal/chartscii_loaders.png) | +| pastel theme with padding of 1 and custom char | ![](./shellfies/horizontal/chartscii_pastel_char.png) | +| pastel theme with naked chart - can be used to create loaders | ![](./shellfies/horizontal/chartscii_loaders.png) | # Unicode issues diff --git a/branding/shellfie.js b/branding/shellfie.js new file mode 100644 index 0000000..6879be6 --- /dev/null +++ b/branding/shellfie.js @@ -0,0 +1,70 @@ +const shellfie = require('shellfie'); +const Chartscii = require('../dist'); + +let color = ''; + +const colors = [ + 'red', + 'green', + 'yellow', + 'blue', + 'purple', + 'pink', + 'cyan', + 'orange', + 'white', + 'marine' +]; + +// generate random chart data +// const data = []; +const labels = ['c', 'h', 'a', 'r', 't', 's', 'c', 'i', 'i', '3.0']; +// for (let i = 0; i < 3; i++) { +// const color = colors[i]; +// const value = Math.floor(Math.random() * 20); +// data.push({ value, color, label: `loading` }); +// } + +const data = [ + { label: "label", value: 1, color: "pink" }, + { label: "label", value: 2, color: "purple" }, + { label: "label", value: 1.5, color: "marine" }, + { label: "label", value: 3, color: "red" }, + { label: "label", value: 2.5, color: "blue" } +]; +const chart = new Chartscii(data, { + // height: 10 + // width: 50 + // width: 50, + // // padding: 2, + // barSize: 1, + padding: 1, + barSize: 1, + valueLabels: true + // naked: true, + // valueLabels: true, + // fill: '░', + // colorLabels: true, + // theme: 'pastel', + // orientation: 'vertical' +}); + + +(async () => { + try { + const data = chart.create(); + console.log(data); + await shellfie(data, { + name: './chartscii_chartpoint', + viewport: { + width: 1024, + height: 300 + }, + }); + } catch (error) { + console.log(error); + } +})(); + + +// termtosvg --still-frames --command 'npx ts-node test.ts' \ No newline at end of file diff --git a/branding/test.ts b/branding/test.ts new file mode 100644 index 0000000..198c2e1 --- /dev/null +++ b/branding/test.ts @@ -0,0 +1,39 @@ +import Chartscii from '../chartscii'; + + + +// const data = [ +// // { value: 18.32, label: '2007' }, +// // { value: 23.23, label: '2008' }, +// // { value: 2.43, label: '2009' }, +// // { value: 5.21, label: '2010' }, +// // { value: 50.97, label: '2011' }, +// // { value: 21.05, label: '2012' }, +// // { value: 1.01, label: '2014' }, +// { value: 183.32, label: '2007' }, +// { value: 231.23, label: '2008' }, +// { value: 26.43, label: '2009' }, +// { value: 50.21, label: '2010' }, +// { value: 508.97, label: '2011' }, +// { value: 212.05, label: '2012' }, +// { value: 10.01, label: '2014' }, +// ] +const data = [ + { label: "label", value: 1, color: "pink" }, + { label: "label", value: 2, color: "purple" }, + { label: "label", value: 1.5, color: "marine" } + ]; + +const chart = new Chartscii(data, { + // width: 50, + // // padding: 2, + // barSize: 1, + // naked: true, + // valueLabels: true, + // fill: '░', + // colorLabels: true, + // theme: 'pastel', + // orientation: 'vertical', +}); + +console.log(chart.create()); \ No newline at end of file diff --git a/formatters/formatter.ts b/formatters/formatter.ts index 8ae602f..7b8a254 100644 --- a/formatters/formatter.ts +++ b/formatters/formatter.ts @@ -27,51 +27,6 @@ abstract class ChartFormatter { stripStyle(label: string) { return label.replace(/\x1b\[[0-9;]*m/g, ''); } - - unicodeRegex() { - // Unicode character classes - const astralRange = '\\ud800-\\udfff'; - const comboMarksRange = '\\u0300-\\u036f'; - const comboHalfMarksRange = '\\ufe20-\\ufe2f'; - const comboSymbolsRange = '\\u20d0-\\u20ff'; - const comboMarksExtendedRange = '\\u1ab0-\\u1aff'; - const comboMarksSupplementRange = '\\u1dc0-\\u1dff'; - const comboRange = comboMarksRange + comboHalfMarksRange + comboSymbolsRange + comboMarksExtendedRange + comboMarksSupplementRange; - const varRange = '\\ufe0e\\ufe0f'; - - // Telugu characters - const teluguVowels = '\\u0c05-\\u0c0c\\u0c0e-\\u0c10\\u0c12-\\u0c14\\u0c60-\\u0c61'; - const teluguVowelsDiacritic = '\\u0c3e-\\u0c44\\u0c46-\\u0c48\\u0c4a-\\u0c4c\\u0c62-\\u0c63'; - const teluguConsonants = '\\u0c15-\\u0c28\\u0c2a-\\u0c39'; - const teluguConsonantsRare = '\\u0c58-\\u0c5a'; - const teluguModifiers = '\\u0c01-\\u0c03\\u0c4d\\u0c55\\u0c56'; - const teluguNumerals = '\\u0c66-\\u0c6f\\u0c78-\\u0c7e'; - const teluguSingle = `[${teluguVowels}(?:${teluguConsonants}(?!\\u0c4d))${teluguNumerals}${teluguConsonantsRare}]`; - const teluguDouble = `[${teluguConsonants}${teluguConsonantsRare}][${teluguVowelsDiacritic}]|[${teluguConsonants}${teluguConsonantsRare}][${teluguModifiers}`; - const teluguTriple = `[${teluguConsonants}]\\u0c4d[${teluguConsonants}]`; - const telugu = `(?:${teluguTriple}|${teluguDouble}|${teluguSingle})`; - - // Unicode capture groups - const astral = `[${astralRange}]`; - const combo = `[${comboRange}]`; - const fitz = '\\ud83c[\\udffb-\\udfff]'; - const modifier = `(?:${combo}|${fitz})`; - const nonAstral = `[^${astralRange}]`; - const regional = '(?:\\ud83c[\\udde6-\\uddff]){2}'; - const surrogatePair = '[\\ud800-\\udbff][\\udc00-\\udfff]'; - const zeroWidthJoiner = '\\u200d'; - const blackFlag = '(?:\\ud83c\\udff4\\udb40\\udc67\\udb40\\udc62\\udb40(?:\\udc65|\\udc73|\\udc77)\\udb40(?:\\udc6e|\\udc63|\\udc6c)\\udb40(?:\\udc67|\\udc74|\\udc73)\\udb40\\udc7f)'; - - // Unicode regexes - const optModifier = `${modifier}?`; - const optVar = `[${varRange}]?`; - const optJoin = `(?:${zeroWidthJoiner}(?:${[nonAstral, regional, surrogatePair].join('|')})${optVar + optModifier})*`; - const seq = optVar + optModifier + optJoin; - const nonAstralCombo = `${nonAstral}${combo}?`; - const symbol = `(?:${[blackFlag, nonAstralCombo, combo, regional, surrogatePair, astral].join('|')})`; - - return new RegExp(`${fitz}(?=${fitz})|${telugu}|${symbol + seq}`, 'g'); - } } export default ChartFormatter; diff --git a/formatters/horizontal.ts b/formatters/horizontal.ts index 2f8df96..e57531c 100644 --- a/formatters/horizontal.ts +++ b/formatters/horizontal.ts @@ -32,16 +32,16 @@ class HorizontalChartFormatter extends ChartFormatter { } formatBar(point: ChartPoint, label: string, barHeight: number, padding: number) { - const repeat = Math.floor(point.scaled / this.options.char.length) + const repeat = point.scaled / this.options.char.length; const color = point.color || this.options.color; const value = this.options.char?.repeat(repeat) + this.formatFill(point); - const bar = this.scaleBar(value, label, color, barHeight, padding); + const bar = this.scaleBar(value, point.value, label, color, barHeight, padding); return point.color ? this.colorify(bar, color) : bar; } - scaleBar(bar: string, label: string, color: string, barHeight: number, padding: number) { + scaleBar(bar: string, value: number, label: string, color: string, barHeight: number, padding: number) { const strippedLabel = this.stripStyle(label); const naked = this.options.naked ? 0 : 1; const space = strippedLabel.length - naked; @@ -49,9 +49,13 @@ class HorizontalChartFormatter extends ChartFormatter { for (let i = 0; i < barHeight; i++) { const char = this.formatStructure(this.options.structure.axis, color); - const pad = i !== 0 ? this.pad(space) + char : ''; - bars.push(pad + bar) + + if (this.options.valueLabels && i === 0) { + bars.push(pad + bar + this.pad(1) + value) + } else { + bars.push(pad + bar) + } } for (let i = 0; i < padding; i++) { @@ -65,14 +69,16 @@ class HorizontalChartFormatter extends ChartFormatter { formatFill(point: ChartPoint) { if (this.options.fill) { - const diff = (this.options.max.scaled - point.scaled); + const diff = (this.options.width - point.scaled); - if (this.options.maxValue) { - const width = this.options.maxValue - point.value; - return this.options.fill.repeat(width); + if (this.options.scale) { + const width = Math.floor(this.options.width - Math.floor(point.scaled)); + if (width > 0) return this.options.fill.repeat(width); } - return this.options.fill.repeat(diff / this.options.fill.length); + if (diff > 0) { + return this.options.fill.repeat(diff / this.options.fill.length); + } } return ''; diff --git a/formatters/vertical.ts b/formatters/vertical.ts index 43df8dc..6497adc 100644 --- a/formatters/vertical.ts +++ b/formatters/vertical.ts @@ -24,7 +24,7 @@ class VerticalChartFormatter extends ChartFormatter { private formatChartScale(length: number) { const charWidth = this.options.char.length; const defaultBarSize = this.options.barSize || 1; - const calculatedBarWidth = Math.floor((this.options.width / (defaultBarSize * length)) / charWidth) || 1; + const calculatedBarWidth = Math.floor((this.options.width / (defaultBarSize * length)) / charWidth) + 1; const barSize = this.options.barSize === undefined ? calculatedBarWidth : this.options.barSize; const calculatedPadding = Math.round((this.options.width / this.chart.length) / charWidth); @@ -37,8 +37,9 @@ class VerticalChartFormatter extends ChartFormatter { } private getMaxHeight(): number { - const maxValue = this.options.maxValue; - return this.options.height || maxValue; + const height = this.options.height + ((this.options.valueLabels && !this.options.fill) ? 1 : 0); + const maxValue = this.options.scale === "auto" ? height : this.options.scale as number; + return height; } private isLongChar(): boolean { @@ -64,7 +65,7 @@ class VerticalChartFormatter extends ChartFormatter { private getCharWidth() { return this.isLongChar() ? this.options.char.length : (this.isFillLonger() ? this.options.fill.length : 1); } - + private getScaledBarSize(barSize: number): number { const { char, fill } = this.getCharLengths(); @@ -91,7 +92,11 @@ class VerticalChartFormatter extends ChartFormatter { const color = point.color; for (let i = 0; i < maxHeight; i++) { - if (i < maxHeight - height) { + if (i === maxHeight - height - 1 && this.options.valueLabels && !this.options.fill) { + const label = this.formatValueLabel(point); + const space = barSize - this.stripStyle(label).length + padding; + verticalChart[i][index] = label + ' '.repeat(space); + } else if (i < maxHeight - height) { const spaces = this.formatSpace(barSize, padding); const fill = this.formatFill(barSize, padding, color); const fills = this.options.fill ? fill : spaces; @@ -146,6 +151,18 @@ class VerticalChartFormatter extends ChartFormatter { return label; } + private formatValueLabel(point: ChartPoint) { + const value = point.value.toString() + + if (this.options.colorLabels) { + const color = point.color || this.options.color; + const coloredLabel = color ? this.colorify(value, color) : value; + return coloredLabel; + } + + return value; + } + private formatLabels(barSize: number, padding: number) { const formatted: string[] = []; this.chart.forEach((point, i) => { @@ -155,7 +172,24 @@ class VerticalChartFormatter extends ChartFormatter { const charLength = this.getCharWidth(); const barWidth = this.isLongChar() ? barSize * charLength + padding : barSize + padding + Math.floor(charLength / 2); const rightPad = Math.abs(barWidth - label.length); - const isFirst = i === 0 ? 1 : 0; + const isFirst = i === 0 && !this.options.naked ? 1 : 0; + formatted.push(' '.repeat(isFirst) + formattedLabel + ' '.repeat(rightPad)); + } + }) + + return formatted.join(''); + } + + private formatValueLabels(barSize: number, padding: number) { + const formatted: string[] = []; + this.chart.forEach((point, i) => { + if (this.options.labels) { + const formattedLabel = this.formatValueLabel(point); + const label = this.stripStyle(formattedLabel); + const charLength = this.getCharWidth(); + const barWidth = this.isLongChar() ? barSize * charLength + padding : barSize + padding + Math.floor(charLength / 2); + const rightPad = Math.abs(barWidth - label.length); + const isFirst = i === 0 && !this.options.naked ? 1 : 0; formatted.push(' '.repeat(isFirst) + formattedLabel + ' '.repeat(rightPad)); } }) @@ -181,7 +215,14 @@ class VerticalChartFormatter extends ChartFormatter { chart.push(''); } - if (this.options.labels) chart.push(this.formatLabels(barSize, padding)); + if (this.options.labels) { + chart.push(this.formatLabels(barSize, padding)); + } + + if (this.options.valueLabels && this.options.fill) { + chart.unshift(''); + chart.unshift(this.formatValueLabels(barSize, padding)); + } return chart.join('\n'); } diff --git a/index.test.ts b/index.test.ts index 5755d7b..5dbffe5 100644 --- a/index.test.ts +++ b/index.test.ts @@ -13,8 +13,7 @@ const colors = [ 'cyan', 'orange', 'purple', - 'pink', - 'blue' + 'marine', ]; const labels = ['c', 'h', 'a', 'r', 't', 's', 'c', 'i', 'i', '🔥']; @@ -22,7 +21,7 @@ function generateChartData() { const data: InputData[] = []; for (let i = 0; i < colors.length; i++) { const color = colors[i]; - data.push({ value: i + 1, label: labels[i], color }); + data.push({ value: (i + 1) * 10, label: labels[i], color }); } return data; } @@ -68,12 +67,11 @@ describe('chartscii tests', () => { const data = generateChartData(); chart = new Chartscii(data, { percentage: true }); const result = [...chart.chart.values()][0]; - expect(result.percentage).to.equal(1.5151515151515151); + expect(result.percentage).to.equal(1.8181818181818181); }); }); describe('examples', () => { - it('should match example', async () => { const data = generateChartData(); const chart = new Chartscii(data, { color: 'pink', colorLabels: true }); @@ -105,7 +103,6 @@ describe('examples', () => { it('should support fill', async () => { const data = generateChartData(); const chart = new Chartscii(data, { fill: '░', colorLabels: true }); - await snap(chart.create(), 'fill'); }); @@ -170,88 +167,108 @@ describe('examples', () => { const chart = new Chartscii(data, { barSize: 2, width: 100, colorLabels: true, percentage: true }); await snap(chart.create(), 'styl3 formatting'); }); + it('should support value labels', async () => { + const data: InputData[] = []; + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; + data.push({ value: i + 1, color, label: `@invert ${i}@` }); + } + const chart = new Chartscii(data, { barSize: 4, fill: '░', colorLabels: true, valueLabels: true }); + + await snap(chart.create(), 'value labels'); + }); + it('should support value labels no fill', async () => { + const data: InputData[] = []; + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; + data.push({ value: i + 1, color, label: `*bold ${i}*` }); + } + const chart = new Chartscii(data, { barSize: 4, colorLabels: true, valueLabels: true }); + + await snap(chart.create(), 'value labels no fill'); + }); }); describe('vertical', () => { it('should support vertical orientation', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { width: 100, color: 'pink', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { width: 100, color: 'pink', colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'vertical'); }); it('should support barSize', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { width: 100, barSize: 5, color: 'green', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { width: 100, barSize: 5, color: 'green', colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'vertical barSize'); }); it('should support color per bar and label', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { width: 100, barSize: 5, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { width: 100, barSize: 5, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'vertical colors'); }); it('should support labeless vertical chart', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { labels: false, orientation: 'vertical' }); + const chart = new Chartscii(data, { labels: false, orientation: 'vertical', }); await snap(chart.create(), 'labeless vertical chart'); }); it('should support labeless colorful chart', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { labels: false, orientation: 'vertical' }); + const chart = new Chartscii(data, { labels: false, orientation: 'vertical', }); await snap(chart.create(), 'labeless color vertical chart'); }); it('should support vertical fill', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { fill: '░', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { fill: '░', colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'vertical fill'); }); it('should support padding', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { fill: '░', padding: 4, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { fill: '░', padding: 4, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'vertical padding'); }); it('should support vertical emoji character', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { char: '🌏', barSize: 2, orientation: 'vertical' }); + const chart = new Chartscii(data, { char: '🌏', barSize: 2, orientation: 'vertical', }); await snap(chart.create(), 'vertical emojis'); }); it('should support pastel theme', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { theme: 'pastel', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { theme: 'pastel', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'pastel theme vertical'); }); it('should support lush theme', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { theme: 'lush', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { theme: 'lush', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'lush theme vertical'); }); it('should support standard theme', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { theme: 'standard', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { theme: 'standard', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'standard theme vertical'); }); it('should support beach theme', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { theme: 'beach', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { theme: 'beach', barSize: 2, width: 100, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'beach theme vertical'); }); it('should support default theme', async () => { const data = generateChartData(); - const chart = new Chartscii(data, { barSize: 2, width: 100, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { barSize: 2, width: 100, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'default theme vertical'); }); @@ -261,37 +278,59 @@ describe('vertical', () => { const color = colors[i]; data.push({ value: i + 1, color, label: `@invert ${i}@` }); } - const chart = new Chartscii(data, { fill: '░', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { fill: '░', colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'styl3 formatting vertical'); }); + + it('should support value labels', async () => { + const data: InputData[] = []; + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; + data.push({ value: i + 1, color, label: `@invert ${i}@` }); + } + const chart = new Chartscii(data, { barSize: 4, fill: '░', colorLabels: true, orientation: 'vertical', valueLabels: true }); + + await snap(chart.create(), 'value labels vertical'); + }); + it('should support value labels no fill', async () => { + const data: InputData[] = []; + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; + data.push({ value: i + 1, color, label: `*bold ${i}*` }); + } + const chart = new Chartscii(data, { barSize: 4, colorLabels: true, orientation: 'vertical', valueLabels: true }); + + await snap(chart.create(), 'value labels vertical no fill'); + }); }); describe('scale', () => { const data = generateChartData(); it('should support char of different widths', async () => { - const chart = new Chartscii(data, { width: 100, color: 'pink', char: '++', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { width: 100, color: 'pink', char: '++', colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'scale char'); }); it('should support char and fill of different widths', async () => { - const chart = new Chartscii(data, { width: 100, color: 'pink', char: '🔥', fill: '🧊', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { width: 100, color: 'pink', char: '🔥', fill: '🧊', colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'scale char widths'); }); it('should support char and fill of different widths non equally', async () => { - const chart = new Chartscii(data, { width: 100, color: 'pink', char: '🔥', fill: '+', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { width: 100, color: 'pink', char: '🔥', fill: '+', colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'scale char widths non equal'); }); it('should support char and fill of different widths non equally reversed', async () => { - const chart = new Chartscii(data, { width: 100, color: 'blue', char: '+', fill: '🔥', colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { color: 'blue', char: '+', fill: '🔥', colorLabels: true, orientation: 'vertical', }); + await snap(chart.create(), 'scale char widths non equal reversed'); }); it('should support padding and width', async () => { - const chart = new Chartscii(data, { width: 150, color: 'cyan', padding: 2, colorLabels: true, orientation: 'vertical' }); + const chart = new Chartscii(data, { width: 150, color: 'cyan', padding: 2, colorLabels: true, orientation: 'vertical', }); await snap(chart.create(), 'scale char padding'); }); diff --git a/options/options.ts b/options/options.ts index 1f0b6d8..c7f852b 100644 --- a/options/options.ts +++ b/options/options.ts @@ -15,6 +15,7 @@ export const defaultOptions: CustomizationOptions = { padding: 0, orientation: 'horizontal', theme: '', + scale: 'auto', structure: { x: '═', y: '╢', diff --git a/package-lock.json b/package-lock.json index b4d9343..6c082a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "3.0.1", "license": "MIT", "dependencies": { - "styl3": "^1.2.4" + "styl3": "3.1.1" }, "devDependencies": { "@types/chai": "4.3.14", @@ -1817,8 +1817,9 @@ } }, "node_modules/styl3": { - "version": "1.4.0", - "license": "MIT" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/styl3/-/styl3-3.1.1.tgz", + "integrity": "sha512-WRTBm/bs2ZfkbIQTk59xfJ0x/X4VKUSm/3u6egDFHNrYCcXYfWauBgq129whvYREvEOlOPQjDu1TARHyxCzCjQ==" }, "node_modules/supports-color": { "version": "8.1.1", diff --git a/package.json b/package.json index 164c4df..ddec2ab 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,6 @@ "typescript": "5.4.5" }, "dependencies": { - "styl3": "^1.2.4" + "styl3": "3.1.1" } } diff --git a/processor/processor.ts b/processor/processor.ts index 2addd9d..8a3c2ec 100644 --- a/processor/processor.ts +++ b/processor/processor.ts @@ -1,4 +1,4 @@ -import { InputData, ChartOptions, ChartData } from '../types/types'; +import { InputData, ChartOptions, ChartData, InputPoint } from '../types/types'; import ChartValidator from '../validator/validator'; class ChartProcessor { @@ -10,9 +10,13 @@ class ChartProcessor { this.options = options; } + getPointValue(point: InputData): number { + return typeof point === "number" ? point : point.value; + } + calculateTotal(data: InputData[]): number { return data.reduce((a, p) => { - const value = typeof p === "number" ? p : p.value; + const value = this.getPointValue(p); return a + value; }, 0); } @@ -21,23 +25,18 @@ class ChartProcessor { const total = this.calculateTotal(data); return data.reduce((a, p) => { - const value = typeof p === "number" ? p : p.value + const value = this.getPointValue(p); const label = typeof p === "number" ? p.toString() : (p.label || p.value.toString()); - const maxValue = this.options.maxValue ? this.options.maxValue : this.options.max.value; - this.options.max.value = value >= maxValue ? value : maxValue; - const scaledValue = this.scale(value); - this.options.max.scaled = scaledValue >= this.options.max.scaled ? scaledValue : this.options.max.scaled; const percentage = this.percentage(value, total); const percentageLength = percentage ? percentage.toFixed(2).length + 5 : 0; - + const maxLabelLength = this.options.percentage ? label.length + percentageLength : label.length; - + if (this.options.labels) this.options.max.label = Math.max(maxLabelLength, this.options.max.label); - if (this.options.labels) { - this.options.max.label = maxLabelLength > this.options.max.label ? maxLabelLength : this.options.max.label; - } + this.options.max.value = Math.max(value, this.options.max.value); + this.options.max.scaled = Math.max(this.scale(value), this.options.max.scaled); return a + value; }, 0); @@ -52,9 +51,18 @@ class ChartProcessor { return 0; } - scale(value: number, maxValue: number = this.options.maxValue) { + scale(value: number) { const size = this.options.orientation === 'vertical' ? this.options.height : this.options.width; - return Math.round((value / this.options.max.value) * (maxValue || size)); + + const { scale, max } = this.options; + + if (scale === "auto") { + return Math.round((value / max.value) * size); + } else if (typeof scale === "number" && scale > 0) { + return Math.round(value / scale) + } else { + return value; + } } preprocess(data: InputData[]): { processed: InputData[], key: string, total: number } { @@ -73,14 +81,17 @@ class ChartProcessor { const chartData = new Map(); processed.forEach((point, i) => { - const { color = this.options.color, label, value } = typeof point === "number" ? { value: point, label: point.toString() } : point; - const scaledValue = this.scale(value); + const { color = this.options.color, label: pointLabel, value } = typeof point === "number" ? { value: point, label: point.toString() } : point; + const scaled = Number(this.scale(value).toFixed(2)); + const percentage = this.percentage(value, total); + const label = pointLabel || value.toString(); + const formattedPoint = { - label: label || value.toString(), + label, value, color, - scaled: scaledValue, - percentage: this.percentage(value, total) + scaled, + percentage } chartData.set(i, formattedPoint); }); @@ -91,8 +102,8 @@ class ChartProcessor { sort(data: InputData[]): InputData[] { if (this.options.sort) { return data.sort((a, b) => { - const first = typeof a === "number" ? a : a.value; - const second = typeof b === "number" ? b : b.value; + const first = this.getPointValue(a); + const second = this.getPointValue(b); return first - second; }); } diff --git a/shellfie.js b/shellfie.js deleted file mode 100644 index fe0529c..0000000 --- a/shellfie.js +++ /dev/null @@ -1,56 +0,0 @@ -const shellfie = require('shellfie'); -const Chartscii = require('./dist'); - -let color = ''; - -const colors = [ - 'red', - 'green', - 'yellow', - 'blue', - 'purple', - 'pink', - 'cyan', - 'orange', - 'white', - 'marine' -]; - -// generate random chart data -const data = []; -const labels = ['c', 'h', 'a', 'r', 't', 's', 'c', 'i', 'i', '3.0']; -for (let i = 0; i < 3; i++) { - const color = colors[i]; - const value = Math.floor(Math.random() * 20); - data.push({ value, color, label: `loading` }); -} -const chart = new Chartscii(data, { - width: 50, - maxValue: 30, - padding: 2, - barSize: 1, - naked: true, - fill: '░', - colorLabels: true, - theme: 'pastel' -}); - - -(async () => { - try { - const data = chart.create(); - console.log(data); - await shellfie(data, { - name: './horizontal/chartscii_loaders', - viewport: { - width: 500, - height: 300 - }, - }); - } catch (error) { - console.log(error); - } -})(); - - -// termtosvg --still-frames --command 'npx ts-node test.ts' \ No newline at end of file diff --git a/shellfies/chartscii_chartpoint.png b/shellfies/chartscii_chartpoint.png index 74b28b91677d93da429c7647e06f9772b740a249..878d9eb4c5171903df180772d360e3bdb5d054b6 100644 GIT binary patch literal 54034 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