Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add base interface of Unicode plot and add implementation of bar plot #5444

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

Vinit-Pandit
Copy link
Contributor

Progress #2010

Description

What is the purpose of this pull request?

This pull request:

  • Add base interface for the unicode plot and implement the bar plot

Related Issues

Does this pull request have any related issues?

This pull request:

Questions

Any questions for reviewers of this pull request?

Yes

  • How much there is need to line chart in unicode ?
  • What are the other plots which we may need in future in unicode ?
  • Does this file structure good ( by the way still in progress ), apart from naming and style and can i go forward with this by adding test and others .

Other

Any other information relevant to this pull request? This may include screenshots, references, and/or implementation notes.

No.

Checklist

Please ensure the following tasks are completed before submitting this pull request.


@stdlib-js/reviewers

unicode bar plot

var barchart = require('@stdlib/plot/unicode/static_plots/bar');

/*

var options = {
    xaxis: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
    yaxis: [10, 1, 5, 6, 7, 22, 10, 35, 40, 10, 11, 12, 13, 15],
    rows: 20,
    cols: 100,
    barColor: 'Frd',
    labelColor: 'Frd'
};

var inst = new barchart(options);

inst.draw();
inst.render();

bar

line char ( under construcution )

line

---
type: pre_push_report
description: Results of running various checks prior to pushing changes.
report:
  - task: run_javascript_examples
    status: na
  - task: run_c_examples
    status: na
  - task: run_cpp_examples
    status: na
  - task: run_javascript_readme_examples
    status: na
  - task: run_c_benchmarks
    status: na
  - task: run_cpp_benchmarks
    status: na
  - task: run_fortran_benchmarks
    status: na
  - task: run_javascript_benchmarks
    status: na
  - task: run_julia_benchmarks
    status: na
  - task: run_python_benchmarks
    status: na
  - task: run_r_benchmarks
    status: na
  - task: run_javascript_tests
    status: na
---
… plot

---
type: pre_commit_static_analysis_report
description: Results of running static analysis checks when committing changes.
report:
  - task: lint_filenames
    status: passed
  - task: lint_editorconfig
    status: passed
  - task: lint_markdown
    status: na
  - task: lint_package_json
    status: na
  - task: lint_repl_help
    status: na
  - task: lint_javascript_src
    status: passed
  - task: lint_javascript_cli
    status: na
  - task: lint_javascript_examples
    status: na
  - task: lint_javascript_tests
    status: na
  - task: lint_javascript_benchmarks
    status: na
  - task: lint_python
    status: na
  - task: lint_r
    status: na
  - task: lint_c_src
    status: na
  - task: lint_c_examples
    status: na
  - task: lint_c_benchmarks
    status: na
  - task: lint_c_tests_fixtures
    status: na
  - task: lint_shell
    status: na
  - task: lint_typescript_declarations
    status: na
  - task: lint_typescript_tests
    status: na
  - task: lint_license_headers
    status: passed
---
---
type: pre_commit_static_analysis_report
description: Results of running static analysis checks when committing changes.
report:
  - task: lint_filenames
    status: passed
  - task: lint_editorconfig
    status: passed
  - task: lint_markdown
    status: na
  - task: lint_package_json
    status: na
  - task: lint_repl_help
    status: na
  - task: lint_javascript_src
    status: na
  - task: lint_javascript_cli
    status: na
  - task: lint_javascript_examples
    status: na
  - task: lint_javascript_tests
    status: na
  - task: lint_javascript_benchmarks
    status: na
  - task: lint_python
    status: na
  - task: lint_r
    status: na
  - task: lint_c_src
    status: na
  - task: lint_c_examples
    status: na
  - task: lint_c_benchmarks
    status: na
  - task: lint_c_tests_fixtures
    status: na
  - task: lint_shell
    status: na
  - task: lint_typescript_declarations
    status: na
  - task: lint_typescript_tests
    status: na
  - task: lint_license_headers
    status: passed
---

---
type: pre_push_report
description: Results of running various checks prior to pushing changes.
report:
  - task: run_javascript_examples
    status: na
  - task: run_c_examples
    status: na
  - task: run_cpp_examples
    status: na
  - task: run_javascript_readme_examples
    status: na
  - task: run_c_benchmarks
    status: na
  - task: run_cpp_benchmarks
    status: na
  - task: run_fortran_benchmarks
    status: na
  - task: run_javascript_benchmarks
    status: na
  - task: run_julia_benchmarks
    status: na
  - task: run_python_benchmarks
    status: na
  - task: run_r_benchmarks
    status: na
  - task: run_javascript_tests
    status: na
---
@stdlib-bot stdlib-bot added the Needs Review A pull request which needs code review. label Feb 25, 2025
@Vinit-Pandit
Copy link
Contributor Author

/stdlib update-copyright-years

@stdlib-bot stdlib-bot added the bot: In Progress Pull request is currently awaiting automation. label Feb 25, 2025
@stdlib-bot stdlib-bot removed the bot: In Progress Pull request is currently awaiting automation. label Feb 25, 2025
@Vinit-Pandit
Copy link
Contributor Author

This pr is Intentionally incomplete , i kind of need acknowledgement weather i can go ahead with this or not, I will add the detail detail description which explain this pr .

@Vinit-Pandit Vinit-Pandit marked this pull request as draft February 26, 2025 13:02
@stdlib-bot stdlib-bot removed the Needs Review A pull request which needs code review. label Feb 26, 2025
@Vinit-Pandit
Copy link
Contributor Author

Vinit-Pandit commented Feb 27, 2025

PALLET

Low level Class for unicode/ansi plot

This class maintains a 2D array of characters, colors, and background colors. During the rendering process, these elements are converted into their respective ANSI escape codes.

Features

This class provides methods to manipulate the 2D array, including:

  1. Adding a character, color, or background color to a specific position.
  2. Inserting a string at a specified location.
  3. Rendering the entire array and converting it into ANSI escape codes.

PlotBase

Overview

PlotBase is a foundational class that utilizes the Palette class to structure plots and handle common tasks shared across all plot types. These tasks include:

  1. Managing ticks and labels
  2. Drawing frames
  3. Rendering fundamental structures like rectangles and lines

Drawing Fundamental Structures

The core idea behind drawing each structure is simple:

  • We pass the structure's dimensions to its respective method.
  • For example, the drawRectangle method takes four values—two for the height range and two for the width range.
  • The structure is then stored in terms of its vertices, which are tracked in state variables.
    • this._x stores the x-coordinates of the vertices.
    • this._y stores the y-coordinates of the vertices.

Example Walkthrough

Drawing a Rectangle

  1. Suppose we need to draw a rectangle.
  2. We call the drawRectangle method of the PlotBase class with parameters xb and yb, representing height and width coordinates:
  drawRectangle( [ 0, 3 ], [ 1, 3 ] );  // Height: (0,3), Width: (1,3)
  1. The method processes these coordinates. In the case of a rectangle, it simply fetches the vertices and stores their x and y coordinates before passing them to the draw method.
  2. The draw method stores the respective data in state variables (this._x, this._y).
  3. Each fundamental structure (e.g., a single rectangle in a bar chart or a single line in a line plot) is stored this way.
  4. Once all structures are stored in the state, we perform the build step:
    • This step fills in missing coordinates based on vertices to complete the shape.
    • The Palette class method setElement is then used to set characters at all filled coordinates.
    • Finally, the render step converts all stored encoding names (color, sketch, and background) into their respective ANSI escape codes.
setNonEnumerableReadOnly( Pallete.prototype, 'setcanvas', function setcanvas() {
	var remainwidth = process.stdout.columns - 0;
	var background;
	var canva = '';
	var color;
	var i;
	var j;

	for ( i = this._sketch.length - 1; i >= 0; i-- ) {
		canva += '\r';
		for ( j = 0; j < this._sketch[ 0 ].length && j < remainwidth; j++ ) {
			// If background color is not same as previous then only we need to change it
			if ( !this.isLegal( i, j - 1 ) || this._backgrounds[ i ][ j ] !== this._backgrounds[ i ][ j - 1 ] ) {
				background = ansiColors[ this._backgrounds[ i ][ j ] ];
				canva += background;
			}
			// If forward color is not same as previous then only we need to change it
			if ( !this.isLegal( i, j - 1 ) || this._colors[ i ][ j ] !== this._colors[ i ][ j - 1 ] ) {
				color = ansiColors[ this._colors[ i ][ j ] ];
				canva += color;
			}
			canva += markers[ this._sketch[ i ][ j ] ];
		}
		canva += ansiColors[ 'def' ];
		canva += '\n';
	}
	this.canva = canva;
	return canva;
} );

every type of plot use this base plot to draw ther visulization all scaling work related to the cordinates also done in the specific plot class
Ex :

barPlot

  • This class use the basePlot and provide interface to draw a bar chart .

  • It scale the data points find appropriate width which fit in provided height and width of the container and scale them to the range

img1

imgSm

Questions

  1. The current implementation is still a prototype, and other plot files, like sparkline, follow a different structure. In sparkline, we first set all the state data, and the main logic is written directly in the render method and in my case i am using the functions it will be good if you can have one look onit !.

    • Question: How strictly do I need to follow this structure?
  2. For the line chart, I think we need a more detailed approach. Instead of using , we should use the half-block characters:

    • Examples: , , ,
    • There are also combination blocks like .
    • This would require more logic in the render method to decide whether to use the full block () or a more detailed smaller block ().
    • Question: Is this approach a rabbit hole for our use case? Should we stick with as the unit pixel, or would using more detailed blocks () be a viable approach?
  3. Does my current version, which includes features like choosing colors and setting backgrounds, add unnecessary complexity?

    • Question: Is this level of customization overkill for our use case? Should we simplify it by removing some of these features?

@kgryte , @Planeshifter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the directory "static_plots"? Is there a reason to distinguish between "static" and presumably "dynamic"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actully when i see the sparkline code base i thought it take the dynamic inputs means continues from buffer , so the interface i have designe is for static plots only in which we provide full date before rendering , so thats the reason why i have put the name static_plot .

out.cols = void 0;
out.xaxis = [];
out.yaxis = [];
out.barColor = 'Fbl';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the color naming scheme here? "Fbl" is not immediately obvious.

Copy link
Contributor Author

@Vinit-Pandit Vinit-Pandit Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fbl stands for forward ( it is name i have seen used in ANSI encoding ) color black , we also have the Bbl for background black . we can change the color schema naming afterwards.

};
var inst = new barchart(options);

inst.draw();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the distinction between "draw" and "render"?

var getLabelColor = require( './props/labelColor/get.js' );
var setMarker = require( './props/marker/set.js' );
var getMarker = require( './props/marker/get.js' );
var primitive = require( './../base/lib' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No walking directories like this. This is inherently fragile if working to access the functionality of another package. If an API does not exist in a package, always use package identifiers.

var getRows = require( './props/rows/get.js' );
var setCols = require( './props/cols/set.js' );
var getCols = require( './props/cols/get.js' );
var setBarColor = require( './props/barColor/set.js' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Folder names should always be snakecase, never camelcase. barColor => bar-color.

'value': null
});
}
defineProperty( this, '_primitive', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we do this rather than inherit from a "base" class?


finalCordx = [];
finalCordy = [];
this.addLableX();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo. addLabelX.

/**
* Add x label and tics.
*
* @name addLableX
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typos.

* @name addCategory
* @memberof Primitive.prototype
* @type {Function}
* @returns {void}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to document parameters.


/**
* @name rows
* @memberof Primitive.prototype
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Primitive/BarPlot/ here and elsewhere.

* @throws {TypeError}
*
*/
defineProperty( BarPlot.prototype, 'rows', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does a bar chart have the concept of rows and columns?

* @throws {TypeError}
*
*/
defineProperty( BarPlot.prototype, 'barColor', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I want bars to have different colors?

* @throws {TypeError}
*
*/
defineProperty( BarPlot.prototype, 'labelColor', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I want labels to have different colors?

* @param {number} containerWidth - width of the container
* @returns {Array} array of scale represent width and height of bar
* */
function scaleBar( x, y, containerWidth ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helper functions such as this should be moved to the top of the file in a // FUNCTIONS // section before // MAIN //.

Comment on lines +22 to +25
* Returns the rendering mode.
*
* @private
* @returns {Array} rendering mode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is incorrect.

Comment on lines +34 to +38
* Sets the rendering mode.
*
* @private
* @param {string} color - indicate the color code.
* @throws {TypeError} must be a number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect.


// VARIABLES //

var colors = require( '@stdlib/plot/unicode/static_plots/marker/colors.js' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't reach into packages like this. Find another way. If, in the future, we rename the colors.js file, this will break here, but we cannot possibly know that beforehand because you pierced the package wall. Files within a package should always be considered private.

*/
function set( color ) {
/* eslint-disable no-invalid-this */
if ( !( color in colors ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use in. Use, e.g., array/base/assert/contains.

Comment on lines +22 to +25
* Returns the rendering mode.
*
* @private
* @returns {Array} rendering mode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect.

return;
}
if ( !isNumber( num ) ) {
throw new TypeError( format( 'invalid assignment. must be a array.' ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be an array and yet we are checking for a number.

Comment on lines +22 to +25
* Returns the rendering mode.
*
* @private
* @returns {Array} rendering mode
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect.

*/
function get() {
/* eslint-disable no-invalid-this */
return this._cols;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we returning _cols when this is the marker property?


// VARIABLES //

var markers = require( '@stdlib/plot/unicode/static_plots/marker/markers.js' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why are all these files in bar/*? Why are they not in bar/lib/*?

// VARIABLES //

var defaults = {
'backgroundColor': 'Bwh',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to explain the color naming convention here.


var defaults = {
'backgroundColor': 'Bwh',
'forwardColor': 'Frd',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably, by forward you mean foreground, correct?

var defaults = {
'backgroundColor': 'Bwh',
'forwardColor': 'Frd',
'marker': 'sp'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is sp?

// MAIN //

/**
* Pallete constructor.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this class, I don't have a good idea what is meant by "Pallete" and what this class is useful for.

});

/**
* Display the rendered plots.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have many methods for "rendering". Why build, show, draw, drawFrame, draw*, and render (elsewhere)? It is not clear how these all fit together and why it makes sense to organize the code like this.

});

/**
* @name setSize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably this should be size. You also need to add descriptions.


'use strict';

var colors = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice the documentation in this file:

'[1,1,1,1]': '█'
};

for ( i = 0; i < 10; i++ ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of this loop?

@kgryte
Copy link
Member

kgryte commented Mar 4, 2025

Questions/comments:

  1. Why do we need to distinguish between Pallet and PlotBase? How are you envisioning another package using Pallet independently of PlotBase?
  2. For axes and tick marks, I suggest studying quite closely how D3 does it axis and tick mark generation. You further want to support setting the number of desired ticks.
  3. For desired plot types, see https://github.com/JuliaPlots/UnicodePlots.jl.
  4. For line chart glyphs, see https://github.com/JuliaPlots/UnicodePlots.jl.
  5. It is hard to provide you with solid feedback when the current code is in such a poor state, with incorrect docs, undesired organization, etc. In general, always strive to at least have what it is completed in as pristine a state as possible. This will significantly help facilitate constructive feedback. Otherwise, it is far too easy to get bogged down in the too many to count mistakes, as are currently present in this draft PR.
  6. Expressing plot dimensions in terms of "rows" and "columns", while valid, is not going to match user intuition. Read the code currently in feat: add plot/table/unicode #2407 to get a better idea of what we're looking for.
  7. I suggest spending more time studying D3 and packages such as https://github.com/JuliaPlots/UnicodePlots.jl.

@Vinit-Pandit
Copy link
Contributor Author

Questions/comments:

  1. Why do we need to distinguish between Pallet and PlotBase? How are you envisioning another package using Pallet independently of PlotBase?
  2. For axes and tick marks, I suggest studying quite closely how D3 does it axis and tick mark generation. You further want to support setting the number of desired ticks.
  3. For desired plot types, see https://github.com/JuliaPlots/UnicodePlots.jl.
  4. For line chart glyphs, see https://github.com/JuliaPlots/UnicodePlots.jl.
  5. It is hard to provide you with solid feedback when the current code is in such a poor state, with incorrect docs, undesired organization, etc. In general, always strive to at least have what it is completed in as pristine a state as possible. This will significantly help facilitate constructive feedback. Otherwise, it is far too easy to get bogged down in the too many to count mistakes, as are currently present in this draft PR.
  6. Expressing plot dimensions in terms of "rows" and "columns", while valid, is not going to match user intuition. Read the code currently in feat: add plot/table/unicode #2407 to get a better idea of what we're looking for.
  7. I suggest spending more time studying D3 and packages such as https://github.com/JuliaPlots/UnicodePlots.jl.

Thanks again for the guidance—I’ll iterate based on this feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants