Repository: yaronn/blessed-contrib Branch: master Commit: ecd48d98c4c6 Files: 64 Total size: 184.0 KB Directory structure: gitextract_i23csocn/ ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── examples/ │ ├── bar.js │ ├── carousel.js │ ├── dashboard-random-colors.js │ ├── dashboard.js │ ├── donut.js │ ├── explorer.js │ ├── gauge-list.js │ ├── gauge-stack.js │ ├── gauge.js │ ├── grid-no-border.js │ ├── grid.js │ ├── inline-data/ │ │ ├── bar.js │ │ ├── donut.js │ │ ├── gauge.js │ │ ├── lcd.js │ │ ├── map.js │ │ ├── markdown.js │ │ ├── multi-line-chart.js │ │ ├── picture.js │ │ ├── sparkline.js │ │ └── table.js │ ├── lcd.js │ ├── line-abbreviate.js │ ├── line-fraction.js │ ├── line-random-colors.js │ ├── line-start-above-zero.js │ ├── line-zoomed-in.js │ ├── log.js │ ├── map.js │ ├── markdown.js │ ├── marked-terminal.js │ ├── multi-line-chart.js │ ├── picture.js │ ├── sparkline.js │ ├── stacked-bar.js │ ├── table-color.js │ └── table.js ├── index.d.ts ├── index.js ├── lib/ │ ├── layout/ │ │ ├── carousel.js │ │ └── grid.js │ ├── server-utils.js │ ├── utils.js │ └── widget/ │ ├── canvas.js │ ├── charts/ │ │ ├── bar.js │ │ ├── line.js │ │ └── stacked-bar.js │ ├── donut.js │ ├── gauge-list.js │ ├── gauge.js │ ├── lcd.js │ ├── log.js │ ├── map.js │ ├── markdown.js │ ├── picture.js │ ├── sparkline.js │ ├── table.js │ └── tree.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "env": { "es6": true, "node": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 2015 }, "rules": { "brace-style": "error", "no-irregular-whitespace":"error", "no-octal-escape": "error", "no-octal": "error", "no-proto":"error", "strict":["error", "global"], "no-undef":"error", "no-use-before-define": "off", "indent": ["error", 2], "semi": [2, "always"], "quotes": [2, "single"] } } ================================================ FILE: .gitignore ================================================ .dccache node_modules p.js p1.js p2.js npm-debug.log log.txt weather.query ================================================ FILE: .npmignore ================================================ node_modules p.js p1.js p2.js npm-debug.log docs examples ================================================ FILE: LICENSE.md ================================================ ## MIT License Copyright (c) 2015 Yaron Naveh and blessed-contrib contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ## blessed-contrib Build dashboards (or any other application) using ascii/ansi art and javascript. Friendly to terminals, ssh and developers. Extends [blessed](https://github.com/chjj/blessed) with custom [drawille](https://github.com/madbence/node-drawille) and other widgets. You should also [check WOPR](https://github.com/yaronn/wopr): a markup for creating terminal reports, presentations and infographics. **Contributors:** Yaron Naveh ([@YaronNaveh](http://twitter.com/YaronNaveh)) Chris ([@xcezzz](https://twitter.com/xcezzz)) Miguel Valadas ([@mvaladas](https://github.com/mvaladas)) Liran Tal ([@lirantal](https://github.com/lirantal)) **Demo ([full size](https://raw.githubusercontent.com/yaronn/blessed-contrib/master/docs/images/term3.gif)):** term term ([source code](./examples/dashboard.js)) **Running the demo** git clone https://github.com/yaronn/blessed-contrib.git cd blessed-contrib npm install node ./examples/dashboard.js Works on Linux, OS X and Windows. For Windows follow the [pre requisites](http://webservices20.blogspot.co.il/2015/04/running-terminal-dashboards-on-windows.html). ## Installation (to build custom projects) npm install blessed blessed-contrib ## Usage You can use any of the default widgets of [blessed](https://github.com/chjj/blessed) (texts, lists and etc) or the widgets added in blessed-contrib (described below). A [layout](#layouts) is optional but useful for dashboards. The widgets in blessed-contrib follow the same usage pattern: `````javascript var blessed = require('blessed') , contrib = require('blessed-contrib') , screen = blessed.screen() , line = contrib.line( { style: { line: "yellow" , text: "green" , baseline: "black"} , xLabelPadding: 3 , xPadding: 5 , label: 'Title'}) , data = { x: ['t1', 't2', 't3', 't4'], y: [5, 1, 7, 5] } screen.append(line) //must append before setting data line.setData([data]) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ````` See below for a complete list of widgets. ## Widgets [Line Chart](#line-chart) [Bar Chart](#bar-chart) [Stacked Bar Chart](#stacked-bar-chart) [Map](#map) [Gauge](#gauge) [Stacked Gauge](#stacked-gauge) [Donut](#donut) [LCD Display](#lcd-display) [Rolling Log](#rolling-log) [Picture](#picture) [Sparkline](#sparkline) [Table](#table) [Tree](#tree) [Markdown](#markdown) ### Line Chart line `````javascript var line = contrib.line( { style: { line: "yellow" , text: "green" , baseline: "black"} , xLabelPadding: 3 , xPadding: 5 , showLegend: true , wholeNumbersOnly: false //true=do not show fraction in y axis , label: 'Title'}) var series1 = { title: 'apples', x: ['t1', 't2', 't3', 't4'], y: [5, 1, 7, 5] } var series2 = { title: 'oranges', x: ['t1', 't2', 't3', 't4'], y: [2, 1, 4, 8] } screen.append(line) //must append before setting data line.setData([series1, series2]) ````` **Examples:** [simple line chart](./examples/line-fraction.js), [multiple lines](./examples/multi-line-chart.js), [256 colors](./examples/line-random-colors.js) ### Bar Chart bar `````javascript var bar = contrib.bar( { label: 'Server Utilization (%)' , barWidth: 4 , barSpacing: 6 , xOffset: 0 , maxHeight: 9}) screen.append(bar) //must append before setting data bar.setData( { titles: ['bar1', 'bar2'] , data: [5, 10]}) ````` ### Stacked Bar Chart stacked-bar `````javascript bar = contrib.stackedBar( { label: 'Server Utilization (%)' , barWidth: 4 , barSpacing: 6 , xOffset: 0 //, maxValue: 15 , height: "40%" , width: "50%" , barBgColor: [ 'red', 'blue', 'green' ]}) screen.append(bar) bar.setData( { barCategory: ['Q1', 'Q2', 'Q3', 'Q4'] , stackedCategory: ['US', 'EU', 'AP'] , data: [ [ 7, 7, 5] , [8, 2, 0] , [0, 0, 0] , [2, 3, 2] ] }) ````` ### Map map `````javascript var map = contrib.map({label: 'World Map'}) map.addMarker({"lon" : "-79.0000", "lat" : "37.5000", color: "red", char: "X" }) ````` ### Gauge gauge `````javascript var gauge = contrib.gauge({label: 'Progress', stroke: 'green', fill: 'white'}) gauge.setPercent(25) ````` ### Stacked Gauge stackedgauge Either specify each stacked portion with a `percent` and `stroke`... `````javascript var gauge = contrib.gauge({label: 'Stacked '}) gauge.setStack([{percent: 30, stroke: 'green'}, {percent: 30, stroke: 'magenta'}, {percent: 40, stroke: 'cyan'}]) ````` Or, you can just supply an array of numbers and random colors will be chosen. `````javascript var gauge = contrib.gauge({label: 'Stacked Progress'}) gauge.setStack([30,30,40]) ````` ### Donut donut `````javascript var donut = contrib.donut({ label: 'Test', radius: 8, arcWidth: 3, remainColor: 'black', yPadding: 2, data: [ {percent: 80, label: 'web1', color: 'green'} ] }); ````` Data passed in uses `percent` and `label` to draw the donut graph. Color is optional and defaults to green. `````javascript donut.setData([ {percent: 87, label: 'rcp','color': 'green'}, {percent: 43, label: 'rcp','color': 'cyan'}, ]); ````` Updating the donut is as easy as passing in an array to `setData` using the same array format as in the constructor. Pass in as many objects to the array of data as you want, they will automatically resize and try to fit. However, please note that you will still be restricted to actual screen space. You can also hardcode a specific numeric into the donut's core display instead of the percentage by passing an `percentAltNumber` property to the data, such as: `````javascript var donut = contrib.donut({ label: 'Test', radius: 8, arcWidth: 3, remainColor: 'black', yPadding: 2, data: [ {percentAltNumber: 50, percent: 80, label: 'web1', color: 'green'} ] }); ````` See an example of this in one of the donuts settings on `./examples/donut.js`. ### LCD Display lcd `````javascript var lcd = contrib.lcd( { segmentWidth: 0.06 // how wide are the segments in % so 50% = 0.5 , segmentInterval: 0.11 // spacing between the segments in % so 50% = 0.550% = 0.5 , strokeWidth: 0.11 // spacing between the segments in % so 50% = 0.5 , elements: 4 // how many elements in the display. or how many characters can be displayed. , display: 321 // what should be displayed before first call to setDisplay , elementSpacing: 4 // spacing between each element , elementPadding: 2 // how far away from the edges to put the elements , color: 'white' // color for the segments , label: 'Storage Remaining'}) ````` `````javascript lcd.setDisplay(23 + 'G'); // will display "23G" lcd.setOptions({}) // adjust options at runtime ````` Please see the **examples/lcd.js** for an example. The example provides keybindings to adjust the `segmentWidth` and `segmentInterval` and `strokeWidth` in real-time so that you can see how they manipulate the look and feel. ### Rolling Log log `````javascript var log = contrib.log( { fg: "green" , selectedFg: "green" , label: 'Server Log'}) log.log("new log line") ````` ### Picture (Also check the new blessed [image implementation](https://github.com/chjj/blessed#image-from-box) which has several benefits over this one.) log `````javascript var pic = contrib.picture( { file: './flower.png' , cols: 25 , onReady: ready}) function ready() {screen.render()} ````` note: only png images are supported ### Sparkline spark `````javascript var spark = contrib.sparkline( { label: 'Throughput (bits/sec)' , tags: true , style: { fg: 'blue' }}) sparkline.setData( [ 'Sparkline1', 'Sparkline2'], [ [10, 20, 30, 20] , [40, 10, 40, 50]]) ````` ### Table table `````javascript var table = contrib.table( { keys: true , fg: 'white' , selectedFg: 'white' , selectedBg: 'blue' , interactive: true , label: 'Active Processes' , width: '30%' , height: '30%' , border: {type: "line", fg: "cyan"} , columnSpacing: 10 //in chars , columnWidth: [16, 12, 12] /*in chars*/ }) //allow control the table with the keyboard table.focus() table.setData( { headers: ['col1', 'col2', 'col3'] , data: [ [1, 2, 3] , [4, 5, 6] ]}) ````` ### Tree table `````javascript var tree = contrib.tree({fg: 'green'}) //allow control the table with the keyboard tree.focus() tree.on('select',function(node){ if (node.myCustomProperty){ console.log(node.myCustomProperty); } console.log(node.name); } // you can specify a name property at root level to display root tree.setData( { extended: true , children: { 'Fruit': { children: { 'Banana': {} , 'Apple': {} , 'Cherry': {} , 'Exotics': { children: { 'Mango': {} , 'Papaya': {} , 'Kiwi': { name: 'Kiwi (not the bird!)', myCustomProperty: "hairy fruit" } }} , 'Pear': {}}} , 'Vegetables': { children: { 'Peas': {} , 'Lettuce': {} , 'Pepper': {}}}}}) ````` #### Options * keys : Key to expand nodes. Default : ['enter','default'] * extended : Should nodes be extended/generated by default? Be careful with this setting when using a callback function. Default : false * template : * extend : Suffix "icon" for closed node. Default : '[+]' * retract : Suffix "icon" for opened node. Default : '[-]' * lines : Show lines in tree. Default : true #### Nodes Every node is a hash and it can have custom properties that can be used in "select" event callback. However, there are several special keys : * name * *Type* : `string` * *Desc* : Node name * If the node isn't the root and you don't specify the name, will be set to hash key * *Example* : { name: 'Fruit'} * children * *Type* : `hash` or `function(node){ return children }` * *Desc* : Node children. * The function must return a hash that could have been used as children property * If you use a function, the result will be stored in `node.childrenContent` and `children` * *Example* : * Hash : {'Fruit':{ name: 'Fruit', children:{ 'Banana': {}, 'Cherry': {}}}} * Function : see `examples/explorer.js` * childrenContent * *Type* : `hash` * *Desc* : Children content for internal usage *DO NOT MODIFY* * If `node.children` is a hash, `node.children===node.childrenContent` * If `node.children` is a function, it's used to store the `node.children()` result * You can read this property, but you should never write it. * Usually this will be used to check `if(node.childrenContent)` in your `node.children` function to generate children only once * extended * *Type* : `boolean` * *Desc* : Determine if this node is extended * No effect when the node have no child * Default value for each node will be `treeInstance.options.extended` if the node `extended` option is not set * *Example* : {'Fruit':{ name: 'Fruit', extended: true, children:{ 'Banana': {}, 'Cherry': {}}}} ### Markdown table `````javascript var markdown = contrib.markdown() markdown.setMarkdown('# Hello \n blessed-contrib renders markdown using `marked-terminal`') ````` ### Colors You can use 256 colors ([source](./examples/line-random-colors.js)): `````javascript function randomColor() { return [Math.random() * 255,Math.random()*255, Math.random()*255] } line = contrib.line( { ... , style: { line: randomColor(), text: randomColor(), baseline: randomColor() } }) ````` ### Layouts [Grid](#grid) [Carousel](#carousel) ### Grid A grid layout can auto position your elements in a grid layout. When using a grid, you should not create the widgets, rather specify to the grid which widget to create and with which params. Each widget can span multiple rows and columns. `````javascript var screen = blessed.screen() var grid = new contrib.grid({rows: 12, cols: 12, screen: screen}) //grid.set(row, col, rowSpan, colSpan, obj, opts) var map = grid.set(0, 0, 4, 4, contrib.map, {label: 'World Map'}) var box = grid.set(4, 4, 4, 4, blessed.box, {content: 'My Box'}) screen.render() ````` ### Carousel A carousel layout switches between different views based on time or keyboard activity. One use case is an office dashboard with rotating views: `````javascript var blessed = require('blessed') , contrib = require('./') , screen = blessed.screen() function page1(screen) { var map = contrib.map() screen.append(map) } function page2(screen) { var line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , label: 'Title' }) var data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [0, 0.0695652173913043, 0.11304347826087, 2], style: { line: 'red' } } ] screen.append(line) line.setData(data) } screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); var carousel = new contrib.carousel( [page1, page2] , { screen: screen , interval: 3000 //how often to switch views (set 0 to never swicth automatically) , controlKeys: true //should right and left keyboard arrows control view rotation }) carousel.start() ````` ## Samples ### Terminal Dashboard term **Running the sample** git clone https://github.com/yaronn/blessed-contrib.git cd blessed-contrib npm install node ./examples/dashboard.js **Installation (for a custom dashboard)** npm install blessed npm install blessed-contrib **A simple dashboard** `````javascript var blessed = require('blessed') , contrib = require('blessed-contrib') , screen = blessed.screen() , grid = new contrib.grid({rows: 1, cols: 2, screen: screen}) var line = grid.set(0, 0, 1, 1, contrib.line, { style: { line: "yellow" , text: "green" , baseline: "black"} , xLabelPadding: 3 , xPadding: 5 , label: 'Stocks'}) var map = grid.set(0, 1, 1, 1, contrib.map, {label: 'Servers Location'}) var lineData = { x: ['t1', 't2', 't3', 't4'], y: [5, 1, 7, 5] } line.setData([lineData]) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ````` **Rich dashboard** See [source code](./examples/dashboard.js) ## Troubleshooting If you see questions marks or some (or all) missign characters try running with these env vars to fix encoding / terminal: ````` $> LANG=en_US.utf8 TERM=xterm-256color node your-code.js ````` ## License This library is under the [MIT License](http://opensource.org/licenses/MIT) ## More Information Created by Yaron Naveh ([twitter](http://twitter.com/YaronNaveh), [blog](http://webservices20.blogspot.com/)) ================================================ FILE: examples/bar.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , bar = contrib.bar( { label: 'Server Utilization (%)' , barWidth: 4 , barSpacing: 6 , xOffset: 0 , maxHeight: 9 , height: "40%"}) screen.append(bar) bar.setData( { titles: ['bar1', 'bar2'] , data: [5, 10]}) screen.render() ================================================ FILE: examples/carousel.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() function page1(screen) { var grid = new contrib.grid({rows: 4, cols: 4, screen: screen}) var line = grid.set(1, 0, 2, 2, contrib.line, { style: { line: "yellow" , text: "green" , baseline: "black"} , xLabelPadding: 3 , xPadding: 5 , label: 'Stocks'}) var map = grid.set(1, 2, 2, 2, contrib.map, {label: 'Servers Location'}) var box = blessed.box({content: 'click right-left arrows or wait 3 seconds for the next layout in the carousel', top: '80%', left: '10%'}) screen.append(box) var lineData = { x: ['t1', 't2', 't3', 't4'], y: [5, 1, 7, 5] } line.setData([lineData]) } function page2(screen) { var line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , label: 'Title' }) var data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [0, 0.0695652173913043, 0.11304347826087, 2], style: { line: 'red' } } ] screen.append(line) line.setData(data) var box = blessed.box({content: 'click right-left arrows or wait 3 seconds for the next layout in the carousel', top: '80%', left: '10%'}) screen.append(box) } screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); var carousel = new contrib.carousel( [page1, page2] , { screen: screen , interval: 3000 , controlKeys: true }) carousel.start() ================================================ FILE: examples/dashboard-random-colors.js ================================================ var blessed = require('blessed') , contrib = require('../index') var screen = blessed.screen() //create layout and widgets // Create a random color function randomColor() { return [Math.random() * 255,Math.random()*255, Math.random()*255] } var grid = new contrib.grid({rows: 12, cols: 12, screen: screen}) /** * Donut Options self.options.radius = options.radius || 14; // how wide is it? over 5 is best self.options.arcWidth = options.arcWidth || 4; //width of the donut self.options.yPadding = options.yPadding || 2; //padding from the top */ var donut = grid.set(8, 8, 4, 2, contrib.donut, { label: 'Percent Donut', radius: 16, arcWidth: 4, yPadding: 2, data: [{label: 'Storage', percent: 87}] }) // var latencyLine = grid.set(8, 8, 4, 2, contrib.line, // { style: // { line: "yellow" // , text: "green" // , baseline: "black"} // , xLabelPadding: 3 // , xPadding: 5 // , label: 'Network Latency (sec)'}) var gauge = grid.set(8, 10, 2, 2, contrib.gauge, {label: 'Storage', percent: [80,20]}) var gauge_two = grid.set(2, 9, 2, 3, contrib.gauge, {label: 'Deployment Progress', percent: 80}) var sparkline = grid.set(10, 10, 2, 2, contrib.sparkline, { label: 'Throughput (bits/sec)' , tags: true , style: { fg: 'blue', titleFg: 'white' }}) var bar = grid.set(4, 6, 4, 3, contrib.bar, { label: 'Server Utilization (%)' , barWidth: 4 , barSpacing: 6 , xOffset: 2 , maxHeight: 9}) var table = grid.set(4, 9, 4, 3, contrib.table, { keys: true , fg: 'green' , label: 'Active Processes' , columnSpacing: 1 , columnWidth: [24, 10, 10]}) /* * * LCD Options //these options need to be modified epending on the resulting positioning/size options.segmentWidth = options.segmentWidth || 0.06; // how wide are the segments in % so 50% = 0.5 options.segmentInterval = options.segmentInterval || 0.11; // spacing between the segments in % so 50% = 0.5 options.strokeWidth = options.strokeWidth || 0.11; // spacing between the segments in % so 50% = 0.5 //default display settings options.elements = options.elements || 3; // how many elements in the display. or how many characters can be displayed. options.display = options.display || 321; // what should be displayed before anything is set options.elementSpacing = options.spacing || 4; // spacing between each element options.elementPadding = options.padding || 2; // how far away from the edges to put the elements //coloring options.color = options.color || "white"; */ var lcdLineOne = grid.set(0,9,2,3, contrib.lcd, { label: "LCD Test", segmentWidth: 0.06, segmentInterval: 0.11, strokeWidth: 0.1, elements: 5, display: 3210, elementSpacing: 4, elementPadding: 2 } ); var errorsLine = grid.set(0, 6, 4, 3, contrib.line, { style: { line: randomColor() , text: randomColor() , baseline: randomColor()} , label: 'Errors Rate' , maxY: 60 , showLegend: true }) var transactionsLine = grid.set(0, 0, 6, 6, contrib.line, { showNthLabel: 5 , maxY: 100 , label: 'Total Transactions' , showLegend: true , legend: {width: 10}}) var map = grid.set(6, 0, 6, 6, contrib.map, {label: 'Servers Location'}) var log = grid.set(8, 6, 4, 2, contrib.log, { fg: randomColor() , selectedFg: randomColor() , label: 'Server Log'}) //dummy data var servers = ['US1', 'US2', 'EU1', 'AU1', 'AS1', 'JP1'] var commands = ['grep', 'node', 'java', 'timer', '~/ls -l', 'netns', 'watchdog', 'gulp', 'tar -xvf', 'awk', 'npm install'] //set dummy data on gauge var gauge_percent = 0 setInterval(function() { gauge.setData([gauge_percent, 100-gauge_percent]); gauge_percent++; if (gauge_percent>=100) gauge_percent = 0 }, 200) var gauge_percent_two = 0 setInterval(function() { gauge_two.setData(gauge_percent_two); gauge_percent_two++; if (gauge_percent_two>=100) gauge_percent_two = 0 }, 200); //set dummy data on bar chart function fillBar() { var arr = [] for (var i=0; i 0.99) pct = 0.00; var color = "green"; if (pct >= 0.25) color = "cyan"; if (pct >= 0.5) color = "yellow"; if (pct >= 0.75) color = "red"; donut.setData([ {percent: parseFloat((pct+0.00) % 1).toFixed(2), label: 'storage', 'color': color} ]); pct += 0.01; } setInterval(function() { updateDonut(); screen.render() }, 500) function setLineData(mockData, line) { for (var i=0; i=100) gauge_percent = 0 }, 200) var gauge_percent_two = 0 setInterval(function() { gauge_two.setData(gauge_percent_two); gauge_percent_two++; if (gauge_percent_two>=100) gauge_percent_two = 0 }, 200); //set dummy data on bar chart function fillBar() { var arr = [] for (var i=0; i 0.99) pct = 0.00; var color = "green"; if (pct >= 0.25) color = "cyan"; if (pct >= 0.5) color = "yellow"; if (pct >= 0.75) color = "red"; donut.setData([ {percent: parseFloat((pct+0.00) % 1).toFixed(2), label: 'storage', 'color': color} ]); pct += 0.01; } setInterval(function() { updateDonut(); screen.render() }, 500) function setLineData(mockData, line) { for (var i=0; i 0.99) pct = 0.00; donut.update([ {percent: parseFloat((pct+0.00) % 1).toFixed(2), label: 'rcp','color':[100,200,170]}, {percent: parseFloat((pct+0.25) % 1).toFixed(2), label: 'rcp','color':[128,128,128]}, {percent: parseFloat((pct+0.50) % 1).toFixed(2), label: 'rcp','color':[255,0,0]}, {percentAltNumber: 42, percent: parseFloat((pct+0.75) % 1).toFixed(2), label: 'web1', 'color': [255,128,0]} ]); screen.render(); pct += 0.01; } screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); ================================================ FILE: examples/explorer.js ================================================ var blessed = require('blessed') , contrib = require('../index') , fs = require('fs') , path = require('path') var screen = blessed.screen() //create layout and widgets var grid = new contrib.grid({rows: 1, cols: 2, screen: screen}) var tree = grid.set(0, 0, 1, 1, contrib.tree, { style: { text: "red" } , template: { lines: true } , label: 'Filesystem Tree'}) var table = grid.set(0, 1, 1, 1, contrib.table, { keys: true , fg: 'green' , label: 'Informations' , columnWidth: [24, 10, 10]}) //file explorer var explorer = { name: '/' , extended: true // Custom function used to recursively determine the node path , getPath: function(self){ // If we don't have any parent, we are at tree root, so return the base case if(! self.parent) return ''; // Get the parent node path and add this node name return self.parent.getPath(self.parent)+'/'+self.name; } // Child generation function , children: function(self){ var result = {}; var selfPath = self.getPath(self); try { // List files in this directory var children = fs.readdirSync(selfPath+'/'); // childrenContent is a property filled with self.children() result // on tree generation (tree.setData() call) if (!self.childrenContent) { for(var child in children){ child = children[child]; var completePath = selfPath+'/'+child; if( fs.lstatSync(completePath).isDirectory() ){ // If it's a directory we generate the child with the children generation function result[child] = { name: child, getPath: self.getPath, extended: false, children: self.children }; }else{ // Otherwise children is not set (you can also set it to "{}" or "null" if you want) result[child] = { name: child, getPath: self.getPath, extended: false }; } } }else{ result = self.childrenContent; } } catch (e){} return result; } } //set tree tree.setData(explorer); // Handling select event. Every custom property that was added to node is // available like the "node.getPath" defined above tree.on('select',function(node){ var path = node.getPath(node); var data = []; // The filesystem root return an empty string as a base case if ( path == '') path = '/'; // Add data to right array data.push([path]); data.push(['']); try { // Add results data = data.concat(JSON.stringify(fs.lstatSync(path),null,2).split("\n").map(function(e){return [e]})); table.setData({headers: ['Info'], data: data}); }catch(e){ table.setData({headers: ['Info'], data: [[e.toString()]]}); } screen.render(); }); //set default table table.setData({headers: ['Info'], data: [[]]}) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.key(['tab'], function(ch, key) { if(screen.focused == tree.rows) table.focus(); else tree.focus(); }); tree.focus() screen.render() ================================================ FILE: examples/gauge-list.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , grid = new contrib.grid({rows: 2, cols: 2, hideBorder: true, screen: screen}) , gaugeList = grid.set(0, 0, 1, 2, contrib.gaugeList, { gaugeSpacing: 0, gaugeHeight: 1, gauges: [ {showLabel: false, stack: [{percent: 30, stroke: 'green'}, {percent: 30, stroke: 'magenta'}, {percent: 40, stroke: 'cyan'}] } , {showLabel: false, stack: [{percent: 40, stroke: 'yellow'}, {percent: 20, stroke: 'magenta'}, {percent: 40, stroke: 'green'}] } , {showLabel: false, stack: [{percent: 50, stroke: 'red'}, {percent: 10, stroke: 'magenta'}, {percent: 40, stroke: 'cyan'}] } ] } ) screen.render() screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); ================================================ FILE: examples/gauge-stack.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , grid = new contrib.grid({rows: 2, cols: 2, hideBorder: true, screen: screen}) , gauge1 = grid.set(0, 0, 1, 1, contrib.gauge, {showLabel: false, stack: [{percent: 30, stroke: 'green'}, {percent: 30, stroke: 'magenta'}, {percent: 40, stroke: 'cyan'}] }) screen.render() ================================================ FILE: examples/gauge.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , gauge = contrib.gauge({label: 'Progress'}) screen.append(gauge) gauge.setPercent(25) screen.render() ================================================ FILE: examples/grid-no-border.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , grid = new contrib.grid({rows: 12, cols: 12, hideBorder: true, screen: screen}) , map = grid.set(0, 0, 4, 4, contrib.map, {}) , box = grid.set(4, 4, 4, 4, blessed.box, {content: 'My Box'}) screen.render() ================================================ FILE: examples/grid.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , grid = new contrib.grid({rows: 12, cols: 12, screen: screen}) , map = grid.set(0, 0, 4, 4, contrib.map, {label: 'World Map'}) , box = grid.set(4, 4, 4, 4, blessed.box, {content: 'My Box'}) screen.render() ================================================ FILE: examples/inline-data/bar.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() , bar = contrib.bar( { label: 'Server Utilization (%)' , barWidth: 4 , barSpacing: 6 , xOffset: 0 , maxHeight: 9 , height: "40%" , data: { titles: ['bar1', 'bar2'] , data: [5, 10]} }) screen.append(bar) screen.render() ================================================ FILE: examples/inline-data/donut.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() , donut = contrib.donut( { data: [ { color: 'red', percent: '50', label: 'a'} , { color: 'blue', percent: '20', label: 'b'} , { color: 'yellow', percent: '80', label: 'c'} ] }) screen.append(donut) screen.render() ================================================ FILE: examples/inline-data/gauge.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() , gauge = contrib.gauge({label: 'Progress', percent: 25}) screen.append(gauge) screen.render() ================================================ FILE: examples/inline-data/lcd.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() , grid = new contrib.grid({rows: 12, cols: 12, screen: screen}) , map = grid.set(0, 0, 4, 4, contrib.map, {label: 'World Map'}) , lcd = grid.set(4,4,4,4, contrib.lcd, { label: "LCD Test", segmentWidth: 0.06, segmentInterval: 0.11, strokeWidth: 0.1, elements: 5, display: 3210, elementSpacing: 4, elementPadding: 2 }) screen.render() ================================================ FILE: examples/inline-data/map.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() , map = contrib.map({ label: 'World Map', markers: [ {"lon" : "-79.0000", "lat" : "37.5000", color: "red", char: "X" } , {"lon" : "79.0000", "lat" : "37.5000", color: "blue", char: "O" } ] }) screen.append(map) screen.render() ================================================ FILE: examples/inline-data/markdown.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() , chalk = require('chalk') , markdown = contrib.markdown({markdown: '# Hello \n blessed-contrib renders markdown using `marked-terminal` ' , style: { firstHeading: 'chalk.green.italic' }}) screen.append(markdown) screen.render() ================================================ FILE: examples/inline-data/multi-line-chart.js ================================================ var blessed = require('blessed') , contrib = require('../../index') , screen = blessed.screen() , line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , label: 'Title' , showLegend: true , legend: {width: 12} , data: [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [5, 1, 7, 5], style: { line: 'red' } } , { title: 'us-west', x: ['t1', 't2', 't3', 't4'], y: [2, 4, 9, 8], style: {line: 'yellow'} } , {title: 'eu-north-with-some-long-string', x: ['t1', 't2', 't3', 't4'], y: [22, 7, 12, 1], style: {line: 'blue'} }] }) screen.append(line) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/inline-data/picture.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() var pic = contrib.picture( { base64: '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' , cols: 95 , onReady: ready }) function ready() { screen.render() } screen.append(pic) ================================================ FILE: examples/inline-data/sparkline.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() var spark = contrib.sparkline( { label: 'Sparkline' , tags: true , border: {type: "line", fg: "cyan"} , width: '50%' , height: '50%' , style: { fg: 'blue' } , data: { titles: [ 'Sparkline1', 'Sparkline2'], data: [ [10, 20, 30, 20, 50, 70, 60, 30, 35, 38] , [40, 10, 40, 50, 20, 30, 20, 20, 19, 40] ] } }) screen.append(spark) screen.render() ================================================ FILE: examples/inline-data/table.js ================================================ var blessed = require('blessed') , contrib = require('../../') , screen = blessed.screen() var table = contrib.table( { keys: true , fg: 'white' , interactive: false , label: 'Active Processes' , width: '30%' , height: '30%' , border: {type: "line", fg: "cyan"} , columnSpacing: 10 , columnWidth: [16, 12] , data: { headers: ['col1', 'col2'] , data: [ [1, 2] , [3, 4] , [5, 6] ]} }) screen.append(table) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/lcd.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen(); /* //these options need to be modified epending on the resulting positioning/size options.segmentWidth = options.segmentWidth || 0.06; // how wide are the segments in % so 50% = 0.5 options.segmentInterval = options.segmentInterval || 0.11; // spacing between the segments in % so 50% = 0.5 options.strokeWidth = options.strokeWidth || 0.11; // spacing between the segments in % so 50% = 0.5 //default display settings options.elements = options.elements || 3; // how many elements in the display. or how many characters can be displayed. options.display = options.display || 321; // what should be displayed before anything is set options.elementSpacing = options.spacing || 4; // spacing between each element options.elementPadding = options.padding || 2; // how far away from the edges to put the elements //coloring options.color = options.color || "white"; */ var lcd = contrib.lcd({ label: 'Test', elements: 4 }); screen.append(lcd); setInterval(function(){ var colors = ['green','magenta','cyan','red','blue']; var text = ['A','B','C','D','E','F','G','H','I','J','K','L']; var value = Math.round(Math.random() * 1000); lcd.setDisplay(value + text[value%12]); lcd.setOptions({ color: colors[value%5], elementPadding: 5 }); screen.render(); }, 1000); screen.key(['g'], function(ch, key) { lcd.increaseWidth(); screen.render(); }); screen.key(['h'], function(ch, key) { lcd.decreaseWidth(); screen.render(); }); screen.key(['t'], function(ch, key) { lcd.increaseInterval(); screen.render(); }); screen.key(['y'], function(ch, key) { lcd.decreaseInterval(); screen.render(); }); screen.key(['b'], function(ch, key) { lcd.increaseStroke(); screen.render(); }); screen.key(['n'], function(ch, key) { lcd.decreaseStroke(); screen.render(); }); screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/line-abbreviate.js ================================================ var blessed = require('blessed') , contrib = require('../index') , screen = blessed.screen() , line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , label: 'Title' , abbreviate: true , style: { baseline: 'white' } }) , data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [5, 8800, 99999, 3179000000], style: { line: 'red' } } ] screen.append(line) //must append before setting data line.setData(data) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/line-fraction.js ================================================ var blessed = require('blessed') , contrib = require('../index') , screen = blessed.screen() , line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , label: 'Title' , numYLabels: 7 //, wholeNumbersOnly: true }) , data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [0, 0.0695652173913043, 0.11304347826087, 2], style: { line: 'red' } } ] screen.append(line) //must append before setting data line.setData(data) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/line-random-colors.js ================================================ var blessed = require('blessed') , contrib = require('../index') function randomColor() { return [Math.random() * 255,Math.random()*255, Math.random()*255] } var screen = blessed.screen() , line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , minY: 30 , maxY: 90 , label: 'Title' , style: { line: randomColor(), text: randomColor(), baseline: randomColor() } }) , data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [50, 88, 72, 91], style: { line: 'red' } } ] screen.append(line) //must append before setting data line.setData(data) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/line-start-above-zero.js ================================================ var blessed = require('blessed') , contrib = require('../index') , screen = blessed.screen() , line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , minY: 30 , label: 'Title' , style: { baseline: 'white' } }) , data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [50, 88, 72, 91], style: { line: 'red' } } ] screen.append(line) //must append before setting data line.setData(data) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/line-zoomed-in.js ================================================ var blessed = require('blessed') , contrib = require('../index') , screen = blessed.screen() , line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , minY: 1000 , maxY: 1050 , label: 'Title' , style: { baseline: 'white' } }) , data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [1010, 1040, 1020, 1030], style: { line: 'red' } } ] screen.append(line) //must append before setting data line.setData(data) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/log.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , log = contrib.log( { fg: "green" , label: 'Server Log' , height: "20%" , tags: true , border: {type: "line", fg: "cyan"} }) screen.append(log) var i = 0 setInterval(function() {log.log("new {red-fg}log{/red-fg} line " + i++)}, 500) screen.render() ================================================ FILE: examples/map.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , map = contrib.map({label: 'World Map'}) screen.append(map) map.addMarker({"lon" : "-79.0000", "lat" : "37.5000", color: "red", char: "X" }) screen.render() ================================================ FILE: examples/markdown.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , chalk = require('chalk') , markdown = contrib.markdown() screen.append(markdown) markdown.setOptions({ firstHeading: chalk.red.italic }) markdown.setMarkdown('# Hello \n This is **markdown** printed in the `terminal` 11') screen.render() ================================================ FILE: examples/marked-terminal.js ================================================ const blessed = require("blessed") const contrib = require("../") const screen = blessed.screen() const markdown = contrib.markdown() screen.append(markdown) markdown.setMarkdown("- [x] Checkbox") screen.render() ================================================ FILE: examples/multi-line-chart.js ================================================ var blessed = require('blessed') , contrib = require('../index') , screen = blessed.screen() , line = contrib.line( { width: 80 , height: 30 , left: 15 , top: 12 , xPadding: 5 , label: 'Title' , showLegend: true , legend: {width: 12}}) , data = [ { title: 'us-east', x: ['t1', 't2', 't3', 't4'], y: [5, 1, 7, 5], style: { line: 'red' } } , { title: 'us-west', x: ['t1', 't2', 't3', 't4'], y: [2, 4, 9, 8], style: {line: 'yellow'} } , {title: 'eu-north-with-some-long-string', x: ['t1', 't2', 't3', 't4'], y: [22, 7, 12, 1], style: {line: 'blue'} }] screen.append(line) //must append before setting data line.setData(data) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/picture.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() var pic = contrib.picture( { file: './media/flower.png' , cols: 95 , onReady: ready}) function ready() { screen.render() } screen.append(pic) ================================================ FILE: examples/sparkline.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() var spark = contrib.sparkline( { label: 'Sparkline' , tags: true , border: {type: "line", fg: "cyan"} , width: '50%' , height: '50%' , style: { fg: 'blue' }}) screen.append(spark) spark.setData( [ 'Sparkline1', 'Sparkline2'], [ [10, 20, 30, 20, 50, 70, 60, 30, 35, 38] , [40, 10, 40, 50, 20, 30, 20, 20, 19, 40]]) screen.render() ================================================ FILE: examples/stacked-bar.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , bar = contrib.stackedBar( { label: 'Server Utilization (%)' , barWidth: 4 , barSpacing: 6 , xOffset: 0 //, maxValue: 15 , height: "40%" , width: "50%" , barBgColor: [ 'red', 'blue', 'green' ]}) screen.append(bar) bar.setData( { barCategory: ['Q1', 'Q2', 'Q3', 'Q4'] , stackedCategory: ['US', 'EU', 'AP'] , data: [ [ 7, 7, 5] , [8, 2, 0] , [0, 0, 0] , [2, 3, 2] ] }) screen.render() ================================================ FILE: examples/table-color.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() , colors = require('colors/safe'); var table = contrib.table( { keys: true , fg: 'white' , selectedFg: 'white' , selectedBg: 'blue' , interactive: false , label: 'Active Processes' , width: '80%' , height: '30%' , border: {type: "line", fg: "cyan"} , columnSpacing: 10 , columnWidth: [7, 12, 15]}) table.focus() screen.append(table) table.setData( { headers: ['col1', 'col2', 'col3'] , data: [ [colors.blue('1111'), '22222', '55555'] , ['33333', '44444', '66666'] ]}) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: examples/table.js ================================================ var blessed = require('blessed') , contrib = require('../') , screen = blessed.screen() var table = contrib.table( { keys: true , vi: true , fg: 'white' , selectedFg: 'white' , selectedBg: 'blue' , interactive: true , label: 'Active Processes' , width: '30%' , height: '30%' , border: {type: "line", fg: "cyan"} , columnSpacing: 10 , columnWidth: [16, 12]}) table.focus() screen.append(table) table.setData( { headers: ['col1', 'col2'] , data: [ [1, 2] , [3, 4] , [5, 6] , [7, 8] ]}) screen.key(['escape', 'q', 'C-c'], function(ch, key) { return process.exit(0); }); screen.render() ================================================ FILE: index.d.ts ================================================ import * as Blessed from 'blessed' export = BlessedContrib declare namespace BlessedContrib { export type Optionals = { [P in keyof K]?: T[K] } export type Picker = { [P in K]: T[P]; }; export module Widgets { import IHasOptions = Blessed.Widgets.IHasOptions; import BoxOptions = Blessed.Widgets.BoxOptions; import ListOptions = Blessed.Widgets.ListOptions; import Types = Blessed.Widgets.Types; import ListElementStyle = Blessed.Widgets.ListElementStyle; import BoxElement = Blessed.Widgets.BoxElement; import ListElement = Blessed.Widgets.ListElement; export interface GridOptions { top?: Types.TTopLeft; left?: Types.TTopLeft; right?: Types.TPosition; bottom?: Types.TPosition; rows?: number cols?: number screen: Blessed.Widgets.Screen border?: Blessed.Widgets.Border hideBorder?: boolean } export type WidgetOptions = BoxOptions | BarOptions | StackedBarOptions | CanvasOptions | TreeOptions | TableOptions | PictureOptions | MarkdownOptions | MapOptions | SparklineOptions | LogOptions | LcdOptions | GaugeOptions | GaugeListOptions | DonutOptions export type WidgetElements = BoxElement | BarElement | LineElement | StackedBarElement | CanvasElement | TreeElement | TableElement | PictureElement | MarkdownElement | MapElement | SparklineElement | LogElement | LcdElement | GaugeElement | GaugeListElement | DonutElement export class GridElement extends BoxElement implements IHasOptions { constructor(opts: GridOptions); set S, S extends TreeElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: TreeOptions): TreeElement set S, S extends TableElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: TableOptions): TableElement set S, S extends PictureElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: PictureOptions): PictureElement set S, S extends MarkdownElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: MarkdownOptions): MarkdownElement set S, S extends MapElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: MapOptions): MapElement set S, S extends LogElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: LogOptions): LogElement set S, S extends LcdElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: LcdOptions): LcdElement set S, S extends GaugeElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: GaugeOptions): GaugeElement set S, S extends GaugeListElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: GaugeListOptions): GaugeListElement set S, S extends DonutElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: DonutOptions): DonutElement set S, S extends BarElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: BarOptions): BarElement set S, S extends LineElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: LineOptions): LineElement set S, S extends StackedBarElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: StackedBarOptions): StackedBarElement set S, S extends CanvasElement>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: CanvasOptions): CanvasElement // set S, S extends WidgetElements>(row: number, col: number, rowSpan: number, colSpan: number, obj: T, opt: WidgetOptions): WidgetElements set(...args:any[]): any // set(row: number, col: number, rowSpan: number, colSpan: number, // obj: T, opts?: P ): P // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof bar // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof line // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof stackedBar // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof canvas // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof tree // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof table // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof picture // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof markdown // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof map // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof log // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof lcd // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof gauge // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof gaugeList // set(row: number, col: number, rowSpan: number, colSpan: number, obj: A, opts?: O): T //typeof donut options: GridOptions; } export interface BarData { titles?: string[], data?: number[] } export interface BarOptions extends CanvasOptions { barWidth?: number barSpacing?: number xOffset?: number maxHeight?: number showText?: boolean barBgColor?: string barFgColor?: string } export class BarElement extends CanvasElement implements IHasOptions { constructor(opts: BarOptions); setData(data: BarData): void; options: BarOptions; } export interface LineData { title?: string x?: string[] y?: number[] style?: { line?: string text?: string baseline?: string } } export interface LineOptions extends CanvasOptions { showNthLabel?: boolean style?: { line?: string text?: string baseline?: string } xLabelPadding?: number xPadding?: number numYLabels?: number legend?: { width: number } wholeNumbersOnly?: boolean minY?: number maxY?: number label?: string } export class LineElement extends CanvasElement implements IHasOptions { constructor(opts: LineOptions); options: LineOptions; } export interface StackedBarData { barCategory?: string[] stackedCategory?: string[] data?: Array } export interface StackedBarOptions extends CanvasOptions { barWidth?: number barSpacing?: number xOffset?: number maxValue?: number barBgColor?: string showLegend?: boolean legend?: any showText?: boolean } export class StackedBarElement extends CanvasElement implements IHasOptions { constructor(opts: StackedBarOptions) options: StackedBarOptions; addLegend(bars: any, x: number): void; } export interface CanvasOptions extends BoxOptions { canvasSize?: { width?: number, height?: number } data?: D } export class CanvasElement extends BoxElement implements IHasOptions { constructor(opts: CanvasOptions) options: CanvasOptions; calcSize(): void; setData(data: D): void; setData(titles: string[], data: D): void; canvasSize: { width: number, height: number } } export interface DonutData { percent?: string, label?: string, color?: string } export interface DonutOptions extends CanvasOptions { stroke?: string fill?: string label?: string radius?: number arcWidth?: number spacing?: number remainColor?: string yPadding?: number } export class DonutElement extends CanvasElement implements IHasOptions { constructor(opts: DonutOptions) options: DonutOptions; } export interface GaugeListOptions extends CanvasOptions { } export class GaugeListElement extends CanvasElement implements IHasOptions { constructor(opts: GaugeListOptions) options: GaugeListOptions; } export interface GaugeOptions extends CanvasOptions { percent: number[] stroke?: string fill?: string label?: string stack?: any showLabel?: boolean } export class GaugeElement extends CanvasElement implements IHasOptions { constructor(opts: GaugeOptions) options: GaugeOptions; setPercent(number: number): void; setStack(stack: Array<{ percent: number, stroke: string }>): void; setData(percent: number[]): void; setData(percent: number): void; } export interface LcdOptions extends CanvasOptions { segmentWidth?: number// how wide are the segments in % so 50% = 0.5 segmentInterval?: number// spacing between the segments in % so 50% = 0.550% = 0.5 strokeWidth?: number// spacing between the segments in % so 50% = 0.5 elements?: number// how many elements in the display. or how many characters can be displayed. display?: number// what should be displayed before first call to setDisplay elementSpacing?: number// spacing between each element elementPadding?: number// how far away from the edges to put the elements color?: 'white' // color for the segments label?: 'Storage Remaining' } export class LcdElement extends CanvasElement implements IHasOptions { constructor(opts: LcdOptions) options: LcdOptions; increaseWidth(): void; decreaseWidth(): void; increaseInterval(): void; decreaseInterval(): void; increaseStroke(): void; decreaseStroke(): void; setOptions(options: any): void; setDisplay(display: any): void; } export interface LogOptions extends ListOptions { border: Blessed.Widgets.Border bufferLength?: number logLines?: string[] interactive?: boolean } export class LogElement extends ListElement implements IHasOptions { constructor(opts: LogOptions); options: LogOptions; log(str: string): boolean; emit(str:any): boolean; } export interface MapOptions extends CanvasOptions { } export class MapElement extends CanvasElement implements IHasOptions { constructor(opts: MapOptions) options: MapOptions; } export interface SparklineOptions extends CanvasOptions { } export class SparklineElement extends CanvasElement implements IHasOptions { constructor(opts: CanvasOptions); options: SparklineOptions; setData(...str: any[]): void; } export interface MarkdownOptions extends CanvasOptions { /** * Markdown text to render. */ markdown?: string; markdownStyle?: any; } export class MarkdownElement extends CanvasElement implements IHasOptions { constructor(opts: MarkdownOptions) options: MarkdownOptions; setOptions(options: any): void; setMarkdown(markdown: string): void; } export interface PictureOptions extends CanvasOptions { } export class PictureElement extends CanvasElement implements IHasOptions { constructor(opts: PictureOptions) options: PictureOptions; } export interface TableData { headers?: string[] data?: Array } export interface TableOptions extends CanvasOptions { parent?: any bold?: string columnSpacing?: number columnWidth?: number[] rows?: ListOptions selectedFg?: string selectedBg?: string label?: string fg?: string bg?: string width?: string height?: string border?: object interactive?: string } export class TableElement extends CanvasElement implements IHasOptions { constructor(opts: TableOptions); options: TableOptions; } export interface TreeNode { name?: string, children?: TreeChildren | ((node: TreeNode) => TreeChildren | Promise), childrenContent?: TreeChildren, extended?: boolean, parent?: TreeNode, [custom: string]: any } export type TreeChildren = Record export interface TreeOptions extends BoxOptions { data?: any extended?: boolean keys?: string[] template?: { extend?: string retract?: string lines?: boolean } } export class TreeElement extends BoxElement implements IHasOptions { constructor(opts: TreeOptions) rows: Blessed.Widgets.ListElement nodeLines?: string[] lineNbr?: number data: any options: TreeOptions; setData(data: TreeNode): void } } export module widget { export class Grid extends Widgets.GridElement {} export class Bar extends Widgets.BarElement {} export class Line extends Widgets.LineElement {} export class StackedBar extends Widgets.StackedBarElement {} export class Canvas extends Widgets.CanvasElement {} export class Tree extends Widgets.TreeElement {} export class Table extends Widgets.TableElement {} export class Picture extends Widgets.PictureElement {} export class Markdown extends Widgets.MarkdownElement {} export class Map extends Widgets.MapElement {} export class Log extends Widgets.LogElement {} export class Lcd extends Widgets.LcdElement {} export class Gauge extends Widgets.GaugeElement {} export class GaugeList extends Widgets.GaugeListElement {} export class Donut extends Widgets.DonutElement {} export class Sparkline extends Widgets.SparklineElement {} } export class grid extends Widgets.GridElement {} export function line(options?: Widgets.LineOptions): Widgets.LineElement export function bar(options?: Widgets.BarOptions): Widgets.BarElement export function stackedBar(options?: Widgets.StackedBarOptions): Widgets.StackedBarElement export function canvas(options?: Widgets.CanvasOptions): Widgets.CanvasElement export function tree(options?: Widgets.TreeOptions): Widgets.TreeElement export function table(options?: Widgets.TableOptions): Widgets.TableElement export function picture(options?: Widgets.PictureOptions): Widgets.PictureElement export function markdown(options?: Widgets.MarkdownOptions): Widgets.MarkdownElement export function sparkline(options?: Widgets.SparklineOptions): Widgets.SparklineElement export function map(options?: Widgets.MapOptions): Widgets.MapElement export function log(options?: Widgets.LogOptions): Widgets.LogElement export function lcd(options?: Widgets.LcdOptions): Widgets.LcdElement export function gauge(options?: Widgets.GaugeOptions): Widgets.GaugeElement export function gaugeList(options?: Widgets.GaugeListOptions): Widgets.GaugeListElement export function donut(options?: Widgets.DonutOptions): Widgets.DonutElement } ================================================ FILE: index.js ================================================ exports.grid = require('./lib/layout/grid') exports.carousel = require('./lib/layout/carousel') exports.map = require('./lib/widget/map') exports.canvas = require('./lib/widget/canvas') exports.gauge = require('./lib/widget/gauge.js') exports.gaugeList = require('./lib/widget/gauge-list.js') exports.lcd = require('./lib/widget/lcd.js') exports.donut = require('./lib/widget/donut.js') exports.log = require('./lib/widget/log.js') exports.picture = require('./lib/widget/picture.js') exports.sparkline = require('./lib/widget/sparkline.js') exports.table = require('./lib/widget/table.js') exports.tree = require('./lib/widget/tree.js') exports.markdown = require('./lib/widget/markdown.js') exports.bar = require('./lib/widget/charts/bar') exports.stackedBar = require('./lib/widget/charts/stacked-bar') exports.line = require('./lib/widget/charts/line') exports.OutputBuffer = require('./lib/server-utils').OutputBuffer exports.InputBuffer = require('./lib/server-utils').InputBuffer exports.createScreen = require('./lib/server-utils').createScreen exports.serverError = require('./lib/server-utils').serverError ================================================ FILE: lib/layout/carousel.js ================================================ 'use strict'; function Carousel(pages, options) { this.currPage = 0; this.pages = pages; this.options = options; this.screen = this.options.screen; } Carousel.prototype.move = function() { var i = this.screen.children.length; while (i--) this.screen.children[i].detach(); this.pages[this.currPage](this.screen, this.currPage); this.screen.render(); }; Carousel.prototype.next = function() { this.currPage++; if (this.currPage==this.pages.length){ if (!this.options.rotate) { this.currPage--; return; } else { this.currPage=0; } } this.move(); }; Carousel.prototype.prev = function() { this.currPage--; if (this.currPage<0) { if (!this.options.rotate) { this.currPage++; return; } else { this.currPage=this.pages.length-1; } } this.move(); }; Carousel.prototype.home = function() { this.currPage = 0; this.move(); }; Carousel.prototype.end = function() { this.currPage = this.pages.length -1; this.move(); }; Carousel.prototype.start = function() { var self = this; this.move(); if (this.options.interval) { setInterval(this.next.bind(this), this.options.interval); } if (this.options.controlKeys) { this.screen.key(['right', 'left', 'home', 'end'], function(ch, key) { if (key.name=='right') self.next(); if (key.name=='left') self.prev(); if (key.name=='home') self.home(); if (key.name=='end') self.end(); }); } }; module.exports = Carousel; ================================================ FILE: lib/layout/grid.js ================================================ 'use strict'; var utils = require('../utils'); var widgetSpacing = 0; function Grid(options) { if (!options.screen) throw 'Error: A screen property must be specified in the grid options.\r\n' + 'Note: Release 2.0.0 has breaking changes. Please refer to the README or to https://github.com/yaronn/blessed-contrib/issues/39'; this.options = options; this.options.dashboardMargin = this.options.dashboardMargin || 0; this.cellWidth = ((100 - this.options.dashboardMargin*2) / this.options.cols); this.cellHeight = ((100 - this.options.dashboardMargin*2) / this.options.rows); } Grid.prototype.set = function(row, col, rowSpan, colSpan, obj, opts) { if (obj instanceof Grid) { throw 'Error: A Grid is not allowed to be nested inside another grid.\r\n' + 'Note: Release 2.0.0 has breaking changes. Please refer to the README or to https://github.com/yaronn/blessed-contrib/issues/39'; } var top = row * this.cellHeight + this.options.dashboardMargin; var left = col * this.cellWidth + this.options.dashboardMargin; //var options = JSON.parse(JSON.stringify(opts)); var options = {}; options = utils.MergeRecursive(options, opts); options.top = top + '%'; options.left = left + '%'; options.width = (this.cellWidth * colSpan - widgetSpacing) + '%'; options.height = (this.cellHeight * rowSpan - widgetSpacing) + '%'; if (!this.options.hideBorder) options.border = {type: 'line', fg: this.options.color || 'cyan'}; var instance = obj(options); this.options.screen.append(instance); return instance; }; module.exports = Grid; ================================================ FILE: lib/server-utils.js ================================================ 'use strict'; var url = require('url') , contrib = require('../index') , blessed = require('blessed'); function OutputBuffer(options) { this.isTTY = true; this.columns = options.cols; this.rows = options.rows; this.write = function(s) { s = s.replace('\x1b8', ''); //not clear from where in blessed this code comes from. It forces the terminal to clear and loose existing content. options.res.write(s); }; this.on = function() {}; } function InputBuffer() { this.isTTY = true; this.isRaw = true; this.emit = function() {}; this.setRawMode = function() {}; this.resume = function() {}; this.pause = function() {}; this.on = function() {}; } function serverError(req, res, err) { setTimeout(function() { if (!res.headersSent) res.writeHead(500, {'Content-Type': 'text/plain'}); res.write('\r\n\r\n'+err+'\r\n\r\n'); //restore cursor res.end('\u001b[?25h'); }, 0); return true; } function createScreen(req, res) { var query = url.parse(req.url, true).query; var cols = query.cols || 250; var rows = query.rows || 50; if (cols<=35 || cols>=500 || rows<=5 || rows>=300) { serverError(req, res, 'cols must be bigger than 35 and rows must be bigger than 5'); return null; } res.writeHead(200, {'Content-Type': 'text/plain'}); var output = new contrib.OutputBuffer({res: res, cols: cols, rows: rows}); var input = new contrib.InputBuffer(); //required to run under forever since it replaces stdin to non-tty var program = blessed.program({output: output, input: input}); if (query.terminal) program.terminal = query.terminal; if (query.isOSX) program.isOSXTerm = query.isOSX; if (query.isiTerm2) program.isiTerm2 = query.isiTerm2; var screen = blessed.screen({program: program}); return screen; } exports.createScreen = createScreen; exports.OutputBuffer = OutputBuffer; exports.InputBuffer = InputBuffer; exports.serverError = serverError; ================================================ FILE: lib/utils.js ================================================ 'use strict'; var x256 = require('x256'); /* * Recursively merge properties of two objects */ function MergeRecursive(obj1, obj2) { if (obj1==null) { return obj2; } if (obj2==null) { return obj1; } for (var p in obj2) { try { // property in destination object set; update its value if ( obj2[p].constructor==Object ) { obj1[p] = MergeRecursive(obj1[p], obj2[p]); } else { obj1[p] = obj2[p]; } } catch(e) { // property in destination object not set; create it and set its value obj1[p] = obj2[p]; } } return obj1; } function getTypeName(thing){ if(thing===null)return '[object Null]'; // special case return Object.prototype.toString.call(thing); } function abbreviateNumber(value) { var newValue = value; if (value >= 1000) { var suffixes = ['', 'k', 'm', 'b','t']; var suffixNum = Math.floor( (''+value).length/3 ); var shortValue = ''; for (var precision = 2; precision >= 1; precision--) { shortValue = parseFloat( (suffixNum != 0 ? (value / Math.pow(1000,suffixNum) ) : value).toPrecision(precision)); var dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g,''); if (dotLessShortValue.length <= 2) { break; } } newValue = shortValue+suffixes[suffixNum]; } return newValue; } function getColorCode(color) { if (Array.isArray(color) && color.length == 3) { return x256(color[0],color[1],color[2]); } else { return color; } } exports.MergeRecursive = MergeRecursive; exports.getTypeName = getTypeName; exports.abbreviateNumber = abbreviateNumber; exports.getColorCode = getColorCode; ================================================ FILE: lib/widget/canvas.js ================================================ 'use strict'; var blessed = require('blessed') , Node = blessed.Node , Box = blessed.Box , InnerCanvas = require('drawille-canvas-blessed-contrib').Canvas; function Canvas(options, canvasType) { var self = this; if (!(this instanceof Node)) { return new Canvas(options); } options = options || {}; this.options = options; Box.call(this, options); this.on('attach', function() { self.calcSize(); self._canvas = new InnerCanvas(this.canvasSize.width, this.canvasSize.height, canvasType); self.ctx = self._canvas.getContext(); if (self.options.data) { self.setData(self.options.data); } }); } Canvas.prototype = Object.create(Box.prototype); Canvas.prototype.type = 'canvas'; Canvas.prototype.calcSize = function() { this.canvasSize = {width: this.width*2-12, height: this.height*4}; }; Canvas.prototype.clear = function() { this.ctx.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height); }; Canvas.prototype.render = function() { this.clearPos(true); var inner = this.ctx._canvas.frame(); this.setContent(inner); return this._render(); }; module.exports = Canvas; ================================================ FILE: lib/widget/charts/bar.js ================================================ 'use strict'; var blessed = require('blessed') , Node = blessed.Node , Canvas = require('../canvas'); function Bar(options) { if (!(this instanceof Node)) { return new Bar(options); } var self = this; Canvas.call(this, options, require('ansi-term')); this.options.barWidth = this.options.barWidth || 6; this.options.barSpacing = this.options.barSpacing || 9; if ((this.options.barSpacing - this.options.barWidth) < 3) { this.options.barSpacing = this.options.barWidth + 3; } this.options.xOffset = this.options.xOffset==null? 5 : this.options.xOffset; if (this.options.showText === false) this.options.showText = false; else this.options.showText = true; this.on('attach', function() { if (self.options.data) { self.setData(self.options.data); } }); } Bar.prototype = Object.create(Canvas.prototype); Bar.prototype.calcSize = function() { this.canvasSize = {width: this.width-2, height: this.height}; }; Bar.prototype.setData = function(bar) { if (!this.ctx) { throw 'error: canvas context does not exist. setData() for bar charts must be called after the chart has been added to the screen via screen.append()'; } this.clear(); var c = this.ctx; var max = Math.max.apply(Math, bar.data); max = Math.max(max, this.options.maxHeight); var x = this.options.xOffset; var barY = this.canvasSize.height - 5; for (var i = 0; i < bar.data.length; i++) { var h = Math.round(barY * (bar.data[i] / max)); if (bar.data[i] > 0) { c.strokeStyle = 'blue'; if (this.options.barBgColor) c.strokeStyle = this.options.barBgColor; c.fillRect(x, barY - h + 1, this.options.barWidth, h); } else { c.strokeStyle = 'normal'; } c.fillStyle = 'white'; if (this.options.barFgColor) c.fillStyle = this.options.barFgColor; if (this.options.showText) c.fillText(bar.data[i].toString(), x + 1, this.canvasSize.height - 4); c.strokeStyle = 'normal'; c.fillStyle = 'white'; if (this.options.labelColor) c.fillStyle = this.options.labelColor; if (this.options.showText) c.fillText(bar.titles[i], x + 1, this.canvasSize.height - 3); x += this.options.barSpacing; } }; Bar.prototype.getOptionsPrototype = function() { return { barWidth: 1 , barSpacing: 1 , xOffset: 1 , maxHeight: 1 , data: { titles: ['s'] , data: [1]} }; }; Bar.prototype.type = 'bar'; module.exports = Bar; ================================================ FILE: lib/widget/charts/line.js ================================================ 'use strict'; var blessed = require('blessed') , Node = blessed.Node , Canvas = require('../canvas') , utils = require('../../utils.js') , _ = require('lodash'); function Line(options) { if (!(this instanceof Node)) { return new Line(options); } options.showNthLabel = options.showNthLabel || 1; options.style = options.style || {}; options.style.line = options.style.line || 'yellow'; options.style.text = options.style.text || 'green'; options.style.baseline = options.style.baseline || 'black'; options.xLabelPadding = options.xLabelPadding || 5; options.xPadding = options.xPadding || 10; options.numYLabels = options.numYLabels || 5; options.legend = options.legend || {}; options.wholeNumbersOnly = options.wholeNumbersOnly || false; options.minY = options.minY || 0; Canvas.call(this, options); } Line.prototype = Object.create(Canvas.prototype); Line.prototype.calcSize = function() { this.canvasSize = {width: this.width*2-12, height: this.height*4-8}; }; Line.prototype.type = 'line'; Line.prototype.setData = function(data) { if (!this.ctx) { throw 'error: canvas context does not exist. setData() for line charts must be called after the chart has been added to the screen via screen.append()'; } //compatability with older api if (!Array.isArray(data)) data = [data]; var self = this; var xLabelPadding = this.options.xLabelPadding; var yLabelPadding = 3; var xPadding = this.options.xPadding; var yPadding = 11; var c = this.ctx; var labels = data[0].x; function addLegend() { if (!self.options.showLegend) return; if (self.legend) self.remove(self.legend); var legendWidth = self.options.legend.width || 15; self.legend = blessed.box({ height: data.length+2, top: 1, width: legendWidth, left: self.width-legendWidth-3, content: '', fg: 'green', tags: true, border: { type: 'line', fg: 'black' }, style: { fg: 'blue', }, screen: self.screen }); var legandText = ''; var maxChars = legendWidth-2; for (let i=0; i max) { max = current; } } } return max + (max - self.options.minY) * 0.2; } function formatYLabel(value, max, min, numLabels, wholeNumbersOnly, abbreviate) { var fixed = (((max - min) / numLabels) < 1 && value!=0 && !wholeNumbersOnly) ? 2 : 0; var res = value.toFixed(fixed); return abbreviate?utils.abbreviateNumber(res):res; } function getMaxXLabelPadding(numLabels, wholeNumbersOnly, abbreviate, min) { var max = getMaxY(); return formatYLabel(max, max, min, numLabels, wholeNumbersOnly, abbreviate).length * 2; } var maxPadding = getMaxXLabelPadding(this.options.numYLabels, this.options.wholeNumbersOnly, this.options.abbreviate, this.options.minY); if (xLabelPadding < maxPadding) { xLabelPadding = maxPadding; } if ((xPadding - xLabelPadding) < 0) { xPadding = xLabelPadding; } function getMaxX() { var maxLength = 0; for(var i = 0; i < labels.length; i++) { if(labels[i] === undefined) { // console.log("label[" + i + "] is undefined"); } else if(labels[i].length > maxLength) { maxLength = labels[i].length; } } return maxLength; } function getXPixel(val) { return ((self.canvasSize.width - xPadding) / labels.length) * val + (xPadding * 1.0) + 2; } function getYPixel(val, minY) { var res = self.canvasSize.height - yPadding - (((self.canvasSize.height - yPadding) / (getMaxY()-minY)) * (val-minY)); res-=2; //to separate the baseline and the data line to separate chars so canvas will show separate colors return res; } // Draw the line graph function drawLine(values, style, minY) { style = style || {}; var color = self.options.style.line; c.strokeStyle = style.line || color; c.moveTo(0, 0); c.beginPath(); c.lineTo(getXPixel(0), getYPixel(values[0], minY)); for(var k = 1; k < values.length; k++) { c.lineTo(getXPixel(k), getYPixel(values[k], minY)); } c.stroke(); } addLegend(); c.fillStyle = this.options.style.text; c.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height); var yLabelIncrement = (getMaxY()-this.options.minY)/this.options.numYLabels; if (this.options.wholeNumbersOnly) yLabelIncrement = Math.floor(yLabelIncrement); //if (getMaxY()>=10) { // yLabelIncrement = yLabelIncrement + (10 - yLabelIncrement % 10) //} //yLabelIncrement = Math.max(yLabelIncrement, 1) // should not be zero if (yLabelIncrement==0) yLabelIncrement = 1; // Draw the Y value texts var maxY = getMaxY(); for(var i = this.options.minY; i < maxY; i += yLabelIncrement) { c.fillText(formatYLabel(i, maxY, this.options.minY, this.options.numYLabels, this.options.wholeNumbersOnly, this.options.abbreviate), xPadding - xLabelPadding, getYPixel(i, this.options.minY)); } for (var h=0; h0) { var calcY = y - currStackHeight; /*fillRect starts from the point bottom of start point so we compensate*/ var calcHeight = Math.max(0, currStackHeight-1); c.fillRect( x, calcY, this.options.barWidth, calcHeight ); c.fillStyle = 'white'; if (this.options.barFgColor) c.fillStyle = this.options.barFgColor; if (this.options.showText) { var str = utils.abbreviateNumber(data.toString()); c.fillText( str, Math.floor(x + this.options.barWidth/2 + str.length/2), calcY + Math.round(calcHeight/2)); } } return currStackHeight; }; StackedBar.prototype.getOptionsPrototype = function() { return { barWidth: 1 , barSpacing: 1 , xOffset: 1 , maxValue: 1 , barBgColor: 's' , data: { barCategory: ['s'] , stackedCategory: ['s'] , data: [ [ 1] ] } }; }; StackedBar.prototype.addLegend = function(bars, x) { var self = this; if (!self.options.showLegend) return; if (self.legend) self.remove(self.legend); var legendWidth = self.options.legend.width || 15; self.legend = blessed.box({ height: bars.stackedCategory.length+2, top: 1, width: legendWidth, left: x, content: '', fg: 'green', tags: true, border: { type: 'line', fg: 'black' }, style: { fg: 'blue', }, screen: self.screen }); var legandText = ''; var maxChars = legendWidth-2; for (var i=0; ip) continue; var si = i-90; var a = slice * si; c.lineTo(Math.round(cx+s*cos(a)), Math.round(cy+s*sin(a))); } c.stroke(); c.closePath(); s++; } } var donuts = data.length; var radius = this.options.radius; var width = this.options.arcWidth; var remainColor = this.options.remainColor; var middle = cheight / 2; var spacing = (cwidth - (donuts * radius * 2)) / (donuts + 1); function drawDonut(label, percent, radius, width, cxx, middle, color, percentAltNumber){ makeRound(100, radius, width, cxx, middle, remainColor ); makeRound(percent, radius, width, cxx, middle, color); var ptext = percentAltNumber ? percentAltNumber.toFixed(0) : parseFloat(percent*100).toFixed(0) + '%'; c.fillText(ptext, cxx - Math.round(parseFloat((c.measureText(ptext).width)/2)) + 3, middle); c.fillText(label, cxx - Math.round(parseFloat((c.measureText(label).width)/2)) + 3, (middle + radius) + 5); } function makeDonut(stat, which){ var left = radius + (spacing * which) + (radius * 2 * (which - 1)); var percent = stat.percent; if (percent > 1.001){ percent = parseFloat(percent / 100).toFixed(2); } var label = stat.label; var percentAltNumber = stat.percentAltNumber; var color = stat.color || 'green'; var cxx = left; drawDonut(label, percent, radius, width, cxx, middle, color, percentAltNumber); } function makeDonuts(stats){ for(var l = 0; l<=stats.length-1;l++){ makeDonut(stats[l], l+1); } } if (data.length){ makeDonuts(data); } this.currentData = data; c.strokeStyle = 'magenta'; c.restore(); return; }; Donut.prototype.getOptionsPrototype = function() { return { spacing: 1, yPadding: 1, radius: 1, arcWidth: 1, data: [ { color: 'red', percent: '50', label: 'a'} , { color: 'blue', percent: '20', label: 'b'} , { color: 'yellow', percent: '80', label: 'c'} ] }; }; module.exports = Donut; ================================================ FILE: lib/widget/gauge-list.js ================================================ 'use strict'; var blessed = require('blessed') , Node = blessed.Node , Canvas = require('./canvas'); function GaugeList(options) { if (!(this instanceof Node)) { return new GaugeList(options); } var self = this; options = options || {}; self.options = options; self.options.stroke = options.stroke || 'magenta'; self.options.fill = options.fill || 'white'; self.options.data = options.data || []; self.options.showLabel = options.showLabel !== false; self.options.gaugeSpacing = options.gaugeSpacing || 0; self.options.gaugeHeight = options.gaugeHeight || 1; Canvas.call(this, options, require('ansi-term')); this.on('attach', function() { var gauges = this.gauges = self.options.gauges; this.setGauges(gauges); }); } GaugeList.prototype = Object.create(Canvas.prototype); GaugeList.prototype.calcSize = function() { this.canvasSize = {width: this.width-2, height: this.height}; }; GaugeList.prototype.type = 'gauge'; GaugeList.prototype.setData = function() { }; GaugeList.prototype.setGauges = function(gauges) { if (!this.ctx) { throw 'error: canvas context does not exist. setData() for gauges must be called after the gauge has been added to the screen via screen.append()'; } var c = this.ctx; c.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height); for (var i=0; i 0){ this.setStack(data); } else if(typeof(data) == typeof(1)) { this.setPercent(data); } }; Gauge.prototype.setPercent = function(percent) { if (!this.ctx) { throw 'error: canvas context does not exist. setData() for gauges must be called after the gauge has been added to the screen via screen.append()'; } var c = this.ctx; c.strokeStyle = this.options.stroke;//'magenta' c.fillStyle = this.options.fill;//'white' c.clearRect(0, 0, this.canvasSize.width, this.canvasSize.height); if (percent < 1.001){ percent = percent * 100; } var width = percent/100*(this.canvasSize.width-3); c.fillRect(1, 2, width, 2); var textX = 7; if (width= 0 && i < this.Elements.length){ this.Elements[i] = parseInt(value, 10); } } } //thx to https://github.com/Enderer/sixteensegment!!! //although it needed HEAVY rework since it was already somewhat busted ;-( function SixteenSegment(count, canvas, width, height, x, y, options){ this.ElementArray = new ElementArray(count); this.SegmentWidth = options.segmentWidth;//(this.ElementWidth * 0.0015) * 5 //0.1; // Width of segments (% of Element Width) this.SegmentInterval = options.segmentInterval;//(this.ElementWidth * 0.0015) * 10 // 0.20; // Spacing between segments (% of Element Width) this.BevelWidth = 0.01; // Size of corner bevel (% of Element Width) this.SideBevelEnabled = true; // Should the sides be beveled this.StrokeLight = options.color; // Color of an on segment outline this.StrokeWidth = options.strokeWidth; // Width of segment outline this.Padding = options.elementPadding; // Padding around the display this.Spacing = options.elementSpacing; // Spacing between elements this.ElementWidth = (width - (this.Spacing*count))/count; this.ElementHeight = height - (this.Padding*2); // console.error("w %s h %s", this.ElementWidth, this.ElementHeight); this.FillLight = 'red'; // Color of an on segment this.FillDark = 'cyan'; // Color of an off segment this.StrokeDark = 'black'; // Color of an off segment outline this.X = 0; this.Y = 0; this.ElementCount = count; this.CalcElementDimensions = CalcElementDimensions; this.FlipVertical = FlipVertical; this.FlipHorizontal = FlipHorizontal; this.CalcPoints = CalcPoints; this.DisplayText = DisplayText; this.Draw = Draw; this.setOptions = setOptions; this.Width = width || canvas.width; this.Height = height || canvas.height; this.Canvas = canvas; this.CalcPoints(); this.ElementArray.SetCount(count); function setOptions(options){ if (options.elements) this.ElementArray.SetCount(options.elements); this.SegmentWidth = options.segmentWidth || this.SegmentWidth; this.SegmentInterval = options.segmentInterval || this.SegmentInterval; this.BevelWidth = 0.01; this.SideBevelEnabled = true; this.StrokeLight = options.color || this.StrokeLight; this.StrokeWidth = options.strokeWidth || this.StrokeWidth; this.Padding = options.elementPadding || this.Padding; this.Spacing = options.elementSpacing || this.Spacing; this.ElementWidth = (width - (this.Spacing*count))/count; this.ElementHeight = height - (this.Padding*2); } function DisplayText(value) { // Recalculate points in case any settings changed // console.error("si: %s, sw: %s", this.SegmentInterval, this.SegmentWidth); // console.error("st: %s", this.StrokeWidth); // Set the display patterns and draw the canvas this.ElementArray.SetText(value, CharacterMasks); this.CalcPoints(); this.Draw(this.Canvas, this.ElementArray.Elements); } function CalcElementDimensions() { var n = this.ElementCount; var h = this.ElementHeight; h -= this.Padding * 2; var w = this.Width; w -= this.Spacing * (n - 1); w -= this.Padding * 2; w /= n; var output = { Width: w, Height: h }; // console.error(output); return output; } function FlipVertical(points, height) { var flipped = []; for(var i=0;i maxX) maxX = this.Points[s][p].x; context.lineTo(Math.round(this.Points[s][p].x), Math.round(this.Points[s][p].y)); } context.closePath(); context.fill(); context.stroke(); if (this.StrokeWidth > 0) { context.stroke(); } } context.translate(elementWidth+this.Spacing, 0); } context.restore(); } function CalcPoints() { var w = this.ElementWidth, h = this.ElementHeight, sw = this.SegmentWidth * w, si = this.SegmentInterval * w, bw = this.BevelWidth * sw, ib = (this.SideBevelEnabled) ? 1 : 0, sf = sw * 0.8, slope = h / w, sqrt2 = Math.SQRT2, sqrt3 = Math.sqrt(3); // Base position of points w/out bevel and interval var w0 = w / 2 - sw / 2, h0 = 0, w1 = w / 2, h1 = sw / 2, w2 = w / 2 + sw / 2, h2 = sw, w3 = w - sw, h3 = h / 2 - sw / 2, w4 = w - sw / 2, h4 = h / 2, w5 = w, h5 = h / 2 + sw / 2; // Order of segments stored in Points[][] var A1 = 0, A2 = 1, B = 2, C = 3, D1 = 4, D2 = 5, E = 6, F = 7, G1 = 8, G2 = 9, H = 10, I = 11, J = 12, K = 13, L = 14, M = 15; // Create the points array for all segments var points = []; points[A1] = [ { x: bw * 2 + si / sqrt2, y: h0 }, { x: w1 - si / 2 - sw / 2 * ib, y: h0 }, { x: w1 - si / 2, y: h1 }, { x: w0 - si / 2, y: h2 }, { x: sw + si / sqrt2, y: h2 }, { x: bw + si / sqrt2, y: h0 + bw } ]; points[G2] = [ { x: w2 + si / sqrt2, y: h3 }, { x: w3 - si / 2 * sqrt3, y: h3 }, { x: w4 - si / 2 * sqrt3, y: h4 }, { x: w3 - si / 2 * sqrt3, y: h5 }, { x: w2 + si / sqrt2, y: h5 }, { x: w1 + si / sqrt2, y: h4 } ]; points[B] = [ { x: w5, y: h0 + bw * 2 + si / sqrt2 }, { x: w5, y: h4 - si / 2 - sw / 2 * ib }, { x: w4, y: h4 - si / 2 }, { x: w3, y: h3 - si / 2 }, { x: w3, y: h2 + si / sqrt2 }, { x: w5 - bw, y: h0 + bw + si / sqrt2 } ]; points[I] = [ { x: w2, y: h2 + si / 2 * sqrt3 }, { x: w2, y: h3 - si / sqrt2 }, { x: w1, y: h4 - si / sqrt2 }, { x: w0, y: h3 - si / sqrt2 }, { x: w0, y: h2 + si / 2 * sqrt3 }, { x: w1, y: h1 + si / 2 * sqrt3 } ]; points[H] = [ { x: (sw + sf) / slope + si, y: h2 + si }, { x: w0 - si, y: w0 * slope - sf - si }, { x: w0 - si, y: h3 - si }, { x: (h3 - sf) / slope - si, y: h3 - si }, { x: sw + si, y: h2 * slope + sf + si }, { x: sw + si, y: h2 + si } ]; points[A2] = this.FlipHorizontal(points[A1], w); // A2 points[C] = this.FlipVertical(points[2], h); // C points[D1] = this.FlipVertical(points[0], h); // D1 points[D2] = this.FlipHorizontal(points[4], w); // D2 points[E] = this.FlipHorizontal(points[3], w); // E points[F] = this.FlipHorizontal(points[2], w); // F points[G1] = this.FlipHorizontal(points[9], w); // G1 points[J] = this.FlipHorizontal(points[10], w); // J points[K] = this.FlipVertical(points[12], h); // K points[L] = this.FlipVertical(points[11], h); // L points[M] = this.FlipVertical(points[10], h); // M this.Points = points; } } var CharacterMasks = (function() { // Segment Bitmasks for individual segments. // Binary Or them together to create bitmasks // a1|a2|b|c|d1|d2|e|f|g1|g2|h|i|j|k|l|m var a1 = 1 << 0, a2 = 1 << 1, b = 1 << 2, c = 1 << 3, d1 = 1 << 4, d2 = 1 << 5, e = 1 << 6, f = 1 << 7, g1 = 1 << 8, g2 = 1 << 9, h = 1 << 10, i = 1 << 11, j = 1 << 12, k = 1 << 13, l = 1 << 14, m = 1 << 15; // Character map associates characters with a bit pattern return { ' ' : 0, '' : 0, '0' : a1|a2|b|c|d1|d2|e|f|j|m, '1' : b|c|j, '2' : a1|a2|b|d1|d2|e|g1|g2, '3' : a1|a2|b|c|d1|d2|g2, '4' : b|c|f|g1|g2, '5' : a1|a2|c|d1|d2|f|g1|g2, '6' : a1|a2|c|d1|d2|e|f|g1|g2, '7' : a1|a2|b|c, '8' : a1|a2|b|c|d1|d2|e|f|g1|g2, '9' : a1|a2|b|c|f|g1|g2, 'A' : e|f|a1|a2|b|c|g1|g2, 'B' : a1|a2|b|c|d1|d2|g2|i|l, 'C' : a1|a2|f|e|d1|d2, 'D' : a1|a2|b|c|d1|d2|i|l, 'E' : a1|a2|f|e|d1|d2|g1|g2, 'F' : a1|a2|e|f|g1 , 'G' : a1|a2|c|d1|d2|e|f|g2, 'H' : b|c|e|f|g1|g2, 'I' : a1|a2|d1|d2|i|l, 'J' : b|c|d1|d2|e, 'K' : e|f|g1|j|k, 'L' : d1|d2|e|f, 'M' : b|c|e|f|h|j, 'N' : b|c|e|f|h|k, 'O' : a1|a2|b|c|d1|d2|e|f, 'P' : a1|a2|b|e|f|g1|g2, 'Q' : a1|a2|b|c|d1|d2|e|f|k, 'R' : a1|a2|b|e|f|g1|g2|k, 'S' : a1|a2|c|d1|d2|f|g1|g2, 'T' : a1|a2|i|l, 'U' : b|c|d1|d2|e|f, 'V' : e|f|j|m, 'W' : b|c|e|f|k|m, 'X' : h|j|k|m, 'Y' : b|f|g1|g2|l, 'Z' : a1|a2|d1|d2|j|m, '-' : g1|g2, '?' : a1|a2|b|g2|l, '+' : g1|g2|i|l, '*' : g1|g2|h|i|j|k|l|m }; }()); module.exports = LCD; ================================================ FILE: lib/widget/log.js ================================================ 'use strict'; var blessed = require('blessed') , Node = blessed.Node , List = blessed.List; function Log(options) { if (!(this instanceof Node)) { return new Log(options); } options = options || {}; options.bufferLength = options.bufferLength || 30; this.options = options; List.call(this, options); this.logLines = []; this.interactive = false; } Log.prototype = Object.create(List.prototype); Log.prototype.log = function(str) { this.logLines.push(str); if (this.logLines.length>this.options.bufferLength) { this.logLines.shift(); } this.setItems(this.logLines); this.scrollTo(this.logLines.length); }; Log.prototype.type = 'log'; module.exports = Log; ================================================ FILE: lib/widget/map.js ================================================ 'use strict'; var blessed = require('blessed') , Node = blessed.Node , Canvas = require('./canvas') , InnerMap = require('map-canvas'); function Map(options) { var self = this; if (!(this instanceof Node)) { return new Map(options); } Canvas.call(this, options); this.on('attach', function() { options.style = options.style || {}; var opts = { excludeAntartica: (options.excludeAntarctica === undefined) ? true : options.excludeAntarctica , disableBackground: (options.disableBackground === undefined) ? true : options.disableBackground , disableMapBackground: (options.disableMapBackground === undefined) ? true : options.disableMapBackground , disableGraticule: (options.disableGraticule === undefined) ? true : options.disableGraticule , disableFill: (options.disableFill === undefined) ? true : options.disableFill , width: self.ctx._canvas.width , height: self.ctx._canvas.height , shapeColor: options.style.shapeColor || 'green'}; opts.startLon = options.startLon || undefined; opts.endLon = options.endLon || undefined; opts.startLat = options.startLat || undefined; opts.endLat = options.endLat || undefined; opts.region = options.region || undefined; opts.labelSpace = options.labelSpace || 5; this.ctx.strokeStyle= options.style.stroke || 'green'; this.ctx.fillStyle=options.style.fill || 'green'; self.innerMap = new InnerMap(opts, this._canvas); self.innerMap.draw(); if (self.options.markers) { for (var m in self.options.markers) { self.addMarker(self.options.markers[m]); } } }); } Map.prototype = Object.create(Canvas.prototype); Map.prototype.calcSize = function() { this.canvasSize = {width: this.width*2-12, height: this.height*4}; }; Map.prototype.type = 'map'; Map.prototype.addMarker = function(options) { if (!this.innerMap) { throw 'error: canvas context does not exist. addMarker() for maps must be called after the map has been added to the screen via screen.append()'; } this.innerMap.addMarker(options); }; Map.prototype.getOptionsPrototype = function() { return { startLon: 10 , endLon: 10 , startLat: 10 , endLat: 10 , region: 'us' , markers: [ {'lon' : '-79.0000', 'lat' : '37.5000', color: 'red', char: 'X' } ,{'lon' : '79.0000', 'lat' : '37.5000', color: 'blue', char: 'O' } ] }; }; Map.prototype.clearMarkers = function() { this.innerMap.draw(); }; module.exports = Map; ================================================ FILE: lib/widget/markdown.js ================================================ 'use strict'; var blessed = require('blessed') , Box = blessed.Box , marked = require('marked') , TerminalRenderer = require('marked-terminal') , chalk = require('chalk'); function Markdown(options) { if (!(this instanceof Box)) { return new Markdown(options); } options = options || {}; const markdownOptions = { style: options.markdownStyle }; this.evalStyles(markdownOptions); this.setOptions(markdownOptions.style); this.options = options; Box.call(this, options); if (options.markdown) this.setMarkdown(options.markdown); } Markdown.prototype = Object.create(Box.prototype); Markdown.prototype.setMarkdown = function(str) { this.setContent(marked.parse(str)); }; Markdown.prototype.setOptions = function(style) { marked.setOptions({ renderer: new TerminalRenderer(style) }); }; Markdown.prototype.evalStyles = function(options) { if (!options.style) return; for (var st in options.style) { if (typeof(options.style[st])!='string') continue; var tokens = options.style[st].split('.'); options.style[st] = chalk; for (var j=1; j=0.0.2", "chalk": "^1.1.0", "drawille-canvas-blessed-contrib": ">=0.1.3", "lodash": "~>=4.17.21", "map-canvas": ">=0.1.5", "marked": "^4.0.12", "marked-terminal": "^5.1.1", "memory-streams": "^0.1.0", "memorystream": "^0.3.1", "picture-tuber": "^1.0.1", "sparkline": "^0.1.1", "strip-ansi": "^3.0.0", "term-canvas": "0.0.5", "x256": ">=0.0.1" } }