blessed-contrib
Advanced tools
Comparing version 1.0.14 to 2.0.0
21
index.js
exports.grid = require('./lib/layout/grid.js') | ||
exports.grid = require('./lib/layout/grid') | ||
exports.carousel = require('./lib/layout/carousel') | ||
exports.map = require('./lib/widget/map.js') | ||
exports.canvas = require('./lib/widget/canvas.js') | ||
exports.map = require('./lib/widget/map') | ||
exports.canvas = require('./lib/widget/canvas') | ||
exports.gauge = require('./lib/widget/gauge.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.gauge = require('./lib/widget/gauge') | ||
exports.log = require('./lib/widget/log') | ||
exports.picture = require('./lib/widget/picture') | ||
exports.sparkline = require('./lib/widget/sparkline') | ||
exports.table = require('./lib/widget/table') | ||
exports.bar = require('./lib/widget/charts/bar.js') | ||
exports.line = require('./lib/widget/charts/line.js') | ||
exports.bar = require('./lib/widget/charts/bar') | ||
exports.line = require('./lib/widget/charts/line') | ||
@@ -16,0 +17,0 @@ exports.OutputBuffer = require('./lib/server-utils').OutputBuffer |
var utils = require('../utils') | ||
var dashboardMargin = 0 | ||
var widgetSpacing = 0 | ||
function Grid(options) { | ||
if (!options.screen) throw "error: a screen property must be specified in the grid options" | ||
this.options = options | ||
this.matrix = [] | ||
for (var i=0; i<options.rows; i++) { | ||
this.matrix[i]=[] | ||
for (var j=0; j<options.cols; j++) { | ||
this.matrix[i][j] = {} | ||
} | ||
} | ||
this.cellWidth = ((100 - dashboardMargin*2) / this.options.cols) | ||
this.cellHeight = ((100 - dashboardMargin*2) / this.options.rows) | ||
} | ||
Grid.prototype.set = function(row, col, rowSpan, colSpan, obj, opts) { | ||
var top = row * this.cellHeight + dashboardMargin | ||
var left = col * this.cellWidth + dashboardMargin | ||
var options = JSON.parse(JSON.stringify(opts)); | ||
options.top = top + '%' | ||
options.left = left + '%' | ||
options.width = (this.cellWidth * colSpan - widgetSpacing) + "%" | ||
options.height = (this.cellHeight * rowSpan - widgetSpacing) + "%" | ||
options.border = {type: "line", fg: "cyan"} | ||
//warn users about breaking changes in version 1.0 | ||
if (utils.getTypeName(rowSpan)!='[object Number]') { | ||
throw 'Error: As of blessed-contrib 1.0, grid.set signature is function(row, col, rowSpan, colSpan, obj, opts). ' + | ||
'Use rowSpan=1, colSpan=1 to imitiate the previous API.' | ||
} | ||
this.matrix[row][col] = {obj: obj, rowSpan: rowSpan, colSpan: colSpan, opts: opts || {}} | ||
var instance = obj(options) | ||
this.options.screen.append(instance) | ||
return instance | ||
} | ||
Grid.prototype.get = function(row, col) { | ||
return this.matrix[row][col].instance | ||
} | ||
//spacing is applyed normally regardless of offsets | ||
//margin need to apply only in the final positioning | ||
Grid.prototype.applyLayout = function(screen, offsetPct) { | ||
var dashboardMargin = 2 | ||
offsetPct = offsetPct || {x: dashboardMargin, y: dashboardMargin, width: 100-dashboardMargin, height: 100-dashboardMargin} | ||
var widgetSpacing = 0 | ||
var width = (100 / this.options.cols - widgetSpacing)*(offsetPct.width/100) | ||
var height = (100 / this.options.rows - widgetSpacing)*(offsetPct.height/100) | ||
for (var i=0; i<this.options.rows; i++) { | ||
for (var j=0; j<this.options.cols; j++) { | ||
if(this.matrix[i][j].obj == null) | ||
continue; | ||
var top = offsetPct.y + i * (height + widgetSpacing) | ||
var left = offsetPct.x + j * (width + widgetSpacing) | ||
if (this.matrix[i][j].obj instanceof Grid) { | ||
var grid = this.matrix[i][j].obj | ||
grid.applyLayout(screen, {x: left, y: top, width: (width * this.matrix[i][j].rowSpan), height: (height * this.matrix[i][j].colSpan)}) | ||
} | ||
else { | ||
this.matrix[i][j].opts.top = top + "%" | ||
this.matrix[i][j].opts.left = left + "%" | ||
this.matrix[i][j].opts.width = (width * this.matrix[i][j].rowSpan) + "%" | ||
this.matrix[i][j].opts.height = (height * this.matrix[i][j].colSpan) + "%" | ||
this.matrix[i][j].opts.border = {type: "line", fg: "cyan"} | ||
this.matrix[i][j].instance = this.matrix[i][j].obj(this.matrix[i][j].opts) | ||
screen.append(this.matrix[i][j].instance) | ||
} | ||
} | ||
} | ||
} | ||
module.exports = Grid |
@@ -13,3 +13,3 @@ var blessed = require('blessed') | ||
} | ||
options.showNthLabel = options.showNthLabel || 1 | ||
@@ -22,5 +22,6 @@ options.style = options.style || {} | ||
options.xPadding = options.xPadding || 10 | ||
options.numYLabels = options.numYlabels || 5 | ||
options.legend = options.legend || {} | ||
options.wholeNumbersOnly = options.wholeNumbersOnly || false | ||
Canvas.call(this, options); | ||
} | ||
@@ -36,3 +37,3 @@ | ||
Line.prototype.setData = function(labels, data) { | ||
Line.prototype.setData = function(data) { | ||
@@ -43,9 +44,47 @@ if (!this.ctx) { | ||
//compatability with older api | ||
if (!Array.isArray(data)) data = [data] | ||
var self = this | ||
var xLabelPadding = this.options.xLabelPadding | ||
var yLabelPadding = 2 | ||
var yLabelPadding = 3 | ||
var xPadding = this.options.xPadding | ||
var yPadding = 10 | ||
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 (var i=0; i<data.length; i++) { | ||
var style = data[i].style || {} | ||
var color = style.line || self.options.style.line | ||
legandText += '{'+color+'-fg}'+ data[i].title.substring(0, maxChars)+'{/'+color+'-fg}\r\n' | ||
} | ||
self.legend.setContent(legandText) | ||
self.append(self.legend) | ||
} | ||
function getMaxY() { | ||
@@ -55,12 +94,15 @@ | ||
for(var i = 0; i < data.length; i ++) { | ||
if(data[i] > max) { | ||
max = data[i]; | ||
} | ||
for(var i = 0; i < data.length; i++) { | ||
for(var j = 0; j < data[i].y.length; j++) { | ||
if(data[i].y[j] > max) { | ||
max = data[i].y[j]; | ||
} | ||
} | ||
} | ||
//max += 25 - max % 25; | ||
max*=1.2 | ||
max = Math.round(max); | ||
//max = Math.round(max); | ||
if (self.options.maxY) { | ||
@@ -73,8 +115,15 @@ return Math.max(max, self.options.maxY) | ||
function getMaxXLabelPadding() { | ||
return getMaxY().toString().length * 2; | ||
function formatYLabel(value, max, numLabels, wholeNumbersOnly) { | ||
var fixed = (max/numLabels<1 && value!=0 && !wholeNumbersOnly) ? 2 : 0 | ||
return value.toFixed(fixed) | ||
} | ||
if (getMaxXLabelPadding() > xLabelPadding) { | ||
xLabelPadding = getMaxXLabelPadding(); | ||
function getMaxXLabelPadding(numLabels, wholeNumbersOnly) { | ||
var max = getMaxY() | ||
return formatYLabel(max, max, numLabels, wholeNumbersOnly).length * 2; | ||
} | ||
if (getMaxXLabelPadding(this.options.numYLabels, this.options.wholeNumbersOnly) > xLabelPadding) { | ||
xLabelPadding = getMaxXLabelPadding(this.options.numYLabels); | ||
}; | ||
@@ -101,13 +150,30 @@ | ||
function getXPixel(val) { | ||
return ((self.canvasSize.width - xPadding) / data.length) * val + (xPadding * 1.0) + 2; | ||
return ((self.canvasSize.width - xPadding) / labels.length) * val + (xPadding * 1.0) + 2; | ||
} | ||
function getYPixel(val) { | ||
var res = self.canvasSize.height - yPadding - (((self.canvasSize.height - yPadding) / getMaxY()) * val); | ||
res-- //to separate the baseline and the data line to separate chars so canvas will show separate colors | ||
function getYPixel(val) { | ||
var res = self.canvasSize.height - yPadding - (((self.canvasSize.height - yPadding) / getMaxY()) * val); | ||
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) { | ||
style = style || {} | ||
var color = self.options.style.line | ||
c.strokeStyle = style.line || color | ||
c.strokeStyle = this.options.style.line | ||
c.moveTo(0, 0) | ||
c.beginPath(); | ||
c.lineTo(getXPixel(0), getYPixel(values[0])); | ||
for(var k = 1; k < values.length; k++) { | ||
c.lineTo(getXPixel(k), getYPixel(values[k])); | ||
} | ||
c.stroke(); | ||
} | ||
addLegend() | ||
c.fillStyle = this.options.style.text | ||
@@ -118,24 +184,23 @@ | ||
var yLabelIncrement = Math.round(getMaxY()/5) | ||
if (getMaxY()>=10) { | ||
yLabelIncrement = yLabelIncrement + (10 - yLabelIncrement % 10) | ||
} | ||
yLabelIncrement = Math.max(yLabelIncrement, 1) // should not be zero | ||
var yLabelIncrement = getMaxY()/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 | ||
for(var i = 0; i < getMaxY(); i += yLabelIncrement) { | ||
c.fillText(i.toString(), xPadding - xLabelPadding, getYPixel(i)); | ||
var maxY = getMaxY() | ||
for(var i = 0; i < maxY; i += yLabelIncrement) { | ||
c.fillText(formatYLabel(i, maxY, this.options.numYLabels, this.options.wholeNumbersOnly), xPadding - xLabelPadding, getYPixel(i)); | ||
} | ||
// Draw the line graph | ||
c.beginPath(); | ||
for (var h=0; h<data.length; h++) { | ||
drawLine(data[h].y, data[h].style) | ||
} | ||
c.lineTo(getXPixel(0), getYPixel(data[0])); | ||
for(var i = 1; i < data.length; i ++) { | ||
c.lineTo(getXPixel(i), getYPixel(data[i])); | ||
} | ||
c.stroke(); | ||
c.strokeStyle = this.options.style.baseline | ||
@@ -155,3 +220,3 @@ | ||
var maxLabelsPossible = charsAvailable / (getMaxX() + 2); | ||
var pointsPerMaxLabel = Math.round(data.length / (maxLabelsPossible)); | ||
var pointsPerMaxLabel = Math.ceil(data[0].y.length / maxLabelsPossible); | ||
var showNthLabel = this.options.showNthLabel; | ||
@@ -162,3 +227,3 @@ if (showNthLabel < pointsPerMaxLabel) { | ||
for(var i = 0; i < data.length; i += showNthLabel) { | ||
for(var i = 0; i < labels.length; i += showNthLabel) { | ||
if((getXPixel(i) + (labels[i].length * 2)) <= this.canvasSize.width) { | ||
@@ -168,2 +233,3 @@ c.fillText(labels[i], getXPixel(i), this.canvasSize.height - yPadding + yLabelPadding); | ||
} | ||
} | ||
@@ -170,0 +236,0 @@ |
@@ -37,3 +37,3 @@ var blessed = require('blessed') | ||
Map.prototype.calcSize = function() { | ||
this.canvasSize = {width: this.width*2-12, height: this.height*4-12} | ||
this.canvasSize = {width: this.width*2-12, height: this.height*4} | ||
} | ||
@@ -40,0 +40,0 @@ |
@@ -12,3 +12,3 @@ var blessed = require('blessed') | ||
options = options || {}; | ||
options.columnSpacing = options.columnSpacing || 30 | ||
options.columnSpacing = options.columnSpacing==null? 10 : options.columnSpacing | ||
options.bold = true | ||
@@ -46,9 +46,7 @@ this.options = options | ||
d.forEach(function(r, i) { | ||
var colsize = self.options.columnSpacing; | ||
if(typeof self.options.columnSpacing == "object") { | ||
colsize = self.options.columnSpacing[i]; | ||
} | ||
var spaceLength = colsize - r.toString().length | ||
colsize = self.options.columnWidth[i]; | ||
r = r.toString().substring(0, colsize) | ||
var spaceLength = colsize - r.length + self.options.columnSpacing | ||
if (spaceLength < 0) { | ||
@@ -55,0 +53,0 @@ spaceLength = 0; |
{ | ||
"name": "blessed-contrib", | ||
"version": "1.0.14", | ||
"version": "2.0.0", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
122
README.md
@@ -33,3 +33,3 @@ ## blessed-contrib | ||
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 bellow). The widgets in blessed-contrib follow the same usage pattern: | ||
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 bellow). A [layout](#layouts) is optional but usefull for dashboards. The widgets in blessed-contrib follow the same usage pattern: | ||
@@ -53,3 +53,3 @@ `````javascript | ||
screen.append(line) //must append before setting data | ||
line.setData(data.x, data.y) | ||
line.setData([data]) | ||
@@ -65,8 +65,21 @@ screen.key(['escape', 'q', 'C-c'], function(ch, key) { | ||
You can also use a layout to position the widgets for you (details in the layouts section). | ||
## Widgets | ||
[Line Chart](#line-chart) | ||
[Bar Chart](#bar-chart) | ||
[Map](#map) | ||
[Gauge](#gauge) | ||
[Rolling Log](#rolling-log) | ||
[Picture](#picture) | ||
[Sparkline](#sparkline) | ||
[Table](#table) | ||
### Line Chart | ||
@@ -84,12 +97,20 @@ | ||
, xPadding: 5 | ||
, showLegend: true | ||
, wholeNumbersOnly: false //true=do not show fraction in y axis | ||
, label: 'Title'}) | ||
var data = { | ||
var series1 = { | ||
title: 'apples', | ||
x: ['t1', 't2', 't3', 't4'], | ||
y: [5, 1, 7, 5] | ||
} | ||
var series2 = { | ||
title: 'oranges', | ||
x: ['t1', 't2', 't3', 't4'], | ||
y: [5, 1, 7, 5] | ||
} | ||
screen.append(line) //must append before setting data | ||
line.setData(data.x, data.y) | ||
line.setData([series1, series2]) | ||
````` | ||
**Examples:** [simple line chart](./examples/line-friction.js), [multiple lines](./examples/multi-line-chart.js) | ||
### Bar Chart | ||
@@ -183,3 +204,4 @@ | ||
, label: 'Active Processes' | ||
, columnSpacing: [16, 12, 12] /*or just 16*/}) | ||
, columnSpacing: 10 //in chars | ||
, columnWidth: [16, 12, 12] /*in chars*/ }) | ||
@@ -190,6 +212,6 @@ //allow control the table with the keyboard | ||
table.setData( | ||
{ headers: ['col1', 'col2'] | ||
{ headers: ['col1', 'col2', 'col3'] | ||
, data: | ||
[ [1, 2] | ||
, [3, 4] ]}) | ||
[ [1, 2, 3] | ||
, [4, 5, 6] ]}) | ||
````` | ||
@@ -199,2 +221,4 @@ | ||
[Grid](#grid) | ||
[Carousel](#carousel) | ||
@@ -205,2 +229,3 @@ ### Grid | ||
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. | ||
@@ -210,27 +235,61 @@ `````javascript | ||
var grid = new contrib.grid({rows: 1, cols: 2}) | ||
var grid = new contrib.grid({rows: 12, cols: 12, screen: screen}) | ||
//grid.set(row, col, rowSpan, colSpan, obj, opts) | ||
grid.set(0, 1, 1, 1, contrib.map, {label: 'World Map'}) | ||
grid.set(0, 1, 1, 1, blessed.box, {content: 'My Box'}) | ||
var map = grid.set(0, 0, 2, 2, contrib.map, {label: 'World Map'}) | ||
var box = grid.set(0, 6, 2, 2, blessed.box, {content: 'My Box'}) | ||
grid.applyLayout(screen) | ||
screen.render | ||
screen.render() | ||
````` | ||
Grids can be nested: | ||
### Carousel | ||
A carousel layout switchs between differen views based on time or keyboard activity. | ||
One use case is office dashboard with rotating views | ||
`````javascript | ||
var grid = new contrib.grid({rows: 1, cols: 2}) | ||
var grid1 = new contrib.grid({rows: 1, cols: 2}) | ||
var blessed = require('blessed') | ||
, contrib = require('./') | ||
, screen = blessed.screen() | ||
grid.set(0, 0, 1, 1, contrib.map, {label: 'World Map'}) | ||
grid1.set(0, 0, 1, 1, blessed.box, {content: 'My Box'}) | ||
grid1.set(0, 1, 1, 1, blessed.box, {content: 'My Box'}) | ||
function page1(screen) { | ||
var map = contrib.map() | ||
screen.append(map) | ||
} | ||
grid.set(0, 1, 1, 1, grid1) | ||
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 | ||
@@ -262,5 +321,5 @@ | ||
, screen = blessed.screen() | ||
, grid = new contrib.grid({rows: 1, cols: 2}) | ||
, grid = new contrib.grid({rows: 1, cols: 2, screen: screen}) | ||
grid.set(0, 0, 1, 1, contrib.line, | ||
var line = grid.set(0, 0, 1, 1, contrib.line, | ||
{ style: | ||
@@ -274,9 +333,4 @@ { line: "yellow" | ||
grid.set(0, 1, 1, 1, contrib.map, {label: 'Servers Location'}) | ||
var map = grid.set(0, 1, 1, 1, contrib.map, {label: 'Servers Location'}) | ||
grid.applyLayout(screen) | ||
var line = grid.get(0, 0) | ||
var map = grid.get(0, 1) | ||
var lineData = { | ||
@@ -287,3 +341,3 @@ x: ['t1', 't2', 't3', 't4'], | ||
line.setData(lineData.x, lineData.y) | ||
line.setData([lineData]) | ||
@@ -290,0 +344,0 @@ screen.key(['escape', 'q', 'C-c'], function(ch, key) { |
30997
617
348