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

Color legend widget #731

Merged
merged 21 commits into from
Sep 19, 2017
Merged

Color legend widget #731

merged 21 commits into from
Sep 19, 2017

Conversation

matthewma7
Copy link

Implementation of color legend widget.

Support both discrete and continuous scale.
Scale calculation is based on d3 scale.
Discrete color scale supports linear log, sqrt, pow, quantile, ordinal.
Continuous color supports linear log, sqrt, pow. Continuous color supports two color transition.

This is is WIP PR, will test use this widget in a project first.

2017-08-17_22-45-01

@aashish24
Copy link
Member

this is looking awesome @MatthewMa

@matthewma7 matthewma7 changed the title [WIP] Color legend widget Color legend widget Aug 31, 2017
@matthewma7
Copy link
Author

This is ready for review

var registerWidget = require('../registry').registerWidget;

/**
* @typedef {object} [geo.gui.colorLegendWidget.category]
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't want the [] around the typedef.

* @alias geo.gui.colorLegendWidget
* @extends geo.gui.domWidget
* @param {object} [arg] Widget options.
* @param {object} [args.position] Position setting relatively to the map container.
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be arg.position not args.position (and similar on lines below).

/**
* @typedef {object} [geo.gui.colorLegendWidget.category]
* @property {string} name The text label of the legend.
* @property {string} type The type of the legend, either discrete or continuous.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be a default value? Perhaps default this to continuous and and the scale to linear.

* @property {string} scale The scale of the legend. For discrete type,
* linear, log, sqrt, pow, ordinal, and quantile is supported.
* For continuous type, linear, log, sqrt, and pow is supported.
* @property {number[]|string[]} domain Only for ordinal scale legend, string values are acceptable. For ordinal legend, the number in the domain array should be the same number of colors. For quantile scale legend, the domain should be an array of all values. For other scales, the domain needs to be an array of two number for marking the upper bound and lower bound.
Copy link
Contributor

Choose a reason for hiding this comment

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

Elsewhere we've wrapped comments at 80 columns (and in general, @aashish24 at least has preferred wrapped lines).

* linear, log, sqrt, pow, ordinal, and quantile is supported.
* For continuous type, linear, log, sqrt, and pow is supported.
* @property {number[]|string[]} domain Only for ordinal scale legend, string values are acceptable. For ordinal legend, the number in the domain array should be the same number of colors. For quantile scale legend, the domain should be an array of all values. For other scales, the domain needs to be an array of two number for marking the upper bound and lower bound.
* @property {string[]} colors The colors of the legend. All valid svg color can be used. For discrete type, multiple values are accepted. For continuous type, an array of two values is supported.
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps add a note that this corresponds to the d3.scale.range parameter. Also, allowing only svg colors is more restrictive then how colors are supported elsewhere. We should pass the colors through geo.util.convertColorToHex with allowAlpha set to true. This would ensure a consistent color definition in geojs. In which, the parameter type can be changed to geo.geoColor.

m_this._renderAxis(svg, axis);
}

function createAxis(axisScale) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please document.

* For continuous type, linear, log, sqrt, and pow is supported.
* @property {number[]|string[]} domain Only for ordinal scale legend, string values are acceptable. For ordinal legend, the number in the domain array should be the same number of colors. For quantile scale legend, the domain should be an array of all values. For other scales, the domain needs to be an array of two number for marking the upper bound and lower bound.
* @property {string[]} colors The colors of the legend. All valid svg color can be used. For discrete type, multiple values are accepted. For continuous type, an array of two values is supported.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

I see some other properties that are optional on categories (base and exponent, I think). They should be added to the typedef as well.

it('Create basic color legend widget', function (done) {
expect($(container).find('.legend').length).toBe(1);
done();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

For synchronous tests, you don't need to use the done parameter -- just use function () and don't call done().

$(legends[1]).find('svg>rect')[0].dispatchEvent(mouseout);
done();
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

The code coverage misses a few things: calling categories() without a parameter and testing that you get the values you set; quantile rendering discrete colors; testing against an unsupported scale; hiding the popup (should that have been triggered by a mouse movement?); mouseenter and mouseleave; a scale with zero categories.

Copy link
Author

Choose a reason for hiding this comment

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

Wow, how do you notice such small misses, thank you for such careful review.
I think I tested popup mouseout event, did the coverage says otherwise? Do you know how to see the line by line code coverage for the colorLegend file?

Copy link
Contributor

Choose a reason for hiding this comment

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

https://codecov.io/gh/OpenGeoscience/geojs/src/2d_legend/src/ui/colorLegendWidget.js

Also, if you run the tests with coverage on locally, it will dump an html coverage report that you can browse.

Copy link
Author

Choose a reason for hiding this comment

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

@jbeezley, Thank you! That's really helpful.

{
"path": "color-legend",
"title": "Color legends",
"exampleCss": ["main.css"],
Copy link
Contributor

Choose a reason for hiding this comment

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

For examples with empty css files, we really should set exampleCss: [] and shouldn't include a blank css file.

@matthewma7 matthewma7 force-pushed the 2d_legend branch 3 times, most recently from bb2c552 to 9f0e27f Compare September 5, 2017 16:39
@matthewma7
Copy link
Author

@manthey, Thank you very much for your detailed previous reviews. They totally make sense.
I have made corresponding changes.
This is ready for further review.

(I don't know why codecov indicates two line is not covered, I have a expect() that tests the results from that two lines)

* container.
* @param {geo.gui.colorLegendWidget.category[]} [arg.categories] An array
* of category definitions for the initial color legends
* @param {number} [arg.width] The pixel width of the wiget in number. Default is 300px.
Copy link
Contributor

Choose a reason for hiding this comment

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

jsdoc lets you specify defaults as part of an optional parameter:

@param {number} [arg.width=300] The width of the widget in pixels.

Copy link
Contributor

Choose a reason for hiding this comment

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

Currently height and margins are hard-coded too. I think that rather than just specifying width and ticks, we should allow width, height, horizontal_margin (or perhaps hmargin), vertical_margin, and ticks. I think that it would be more intuitive if width and height were the size of the color scale, and the horizontal_margin (or whatever we call it) would be the spacing at either end and the vertical_margin would be the spacing at the bottom for the ticks and labels.


if (arg.categories) {
this.categories(arg.categories);
}
};

// clear the DOM container and create legends
/**
* clear the DOM container and create legends
Copy link
Contributor

Choose a reason for hiding this comment

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

This should fail the jsdoc function description template rules, which require a capital letter and terminal punctuation. Please check for other jsdoc errors on this file (they are reported as warnings).

};

// Draw an individual discrete type legend
this._prepareCategories = function (categories) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please comment the function.

@@ -270,8 +297,9 @@ var colorLegendWidget = function (arg) {
if (category.scale === 'pow' && category.exponent) {
axisScale.exponent(category.exponent);
}
var randomString = Math.random().toString(36).substring(5);
var precision = Math.max.apply(null, category.domain.map(function (number) { return getPrecision(number); }));
var randomString = util.randomString(5);
Copy link
Contributor

Choose a reason for hiding this comment

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

I should have noticed before. We already have a function in d3/uniqueID for this purpose, so we should use that.

src/ui/widget.js Outdated
* @property {string|number} bottom The position to the bottom of the container.
* Value is used similarly to the top property.
* @property {string|number} left The position to the left of the container.
* Value is used similarly to the top property.
Copy link
Contributor

Choose a reason for hiding this comment

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

All of these properties are optional. Further, we should add:

@property {*} [...] Additional css properties that affect position are
  allowed.  See the css specification for details.

@manthey
Copy link
Contributor

manthey commented Sep 18, 2017

With PR #733, you can remove examples/color-legend/index.pug and examples/color-legend/main.css, and remove the lines that have path and example.css from examples/color-legend/example.json.

@matthewma7
Copy link
Author

OMG, I finally fixed the build. @manthey, could you take a quick look?

* Formatter of number that tries to maximize the precision
* while making the output shorter.
* @param {number} number to be formatted
* @param {precision} precision number of decimal precision will be tried to be kept
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the data type should just be number. I'd recommend changing this to @param {number} precision Maximum number of decimal places that are kept..

return this;
};

function getPrecision(a) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function needs to be commented (while you at it, _hidePopup, above, needs it too).

function getPrecision(a) {
if (!isFinite(a)) return 0;
var e = 1, p = 0;
while (Math.round(a * e) / e !== a) { e *= 10; p++; }
Copy link
Contributor

Choose a reason for hiding this comment

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

If you pass a value with a magnitude less than 1 / Number.MAX_VALUE, the process can get stuck in an infinite loop (if Number.MIN_VALUE is less than 1 / Number.MAX_VALUE , which is true in my version of Chrome). Perhaps we should return if isFinite(a * e) is false.

@matthewma7 matthewma7 merged commit cb4ea54 into master Sep 19, 2017
@matthewma7 matthewma7 deleted the 2d_legend branch September 19, 2017 18:49
@Chrismarsh
Copy link

I realize this is closed, however I have a question regarding how to use this. When I add a contour plot as well as one of these legends, the colouring of the contour plot doesn't match the legend's colour. Is this to be expected?

@manthey
Copy link
Contributor

manthey commented Oct 2, 2017

Are you using the same colors for the contour plot and the legend? They can be specified independently; there is nothing that forces them to be the same, though you should be able to use the same colors for both.

If you have a code snippet, we can advise further.

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.

5 participants