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

Add getColorScale to enable displaying multiple lines in LineChart #11

Merged
merged 26 commits into from
Mar 5, 2019

Conversation

chenesan
Copy link
Contributor

Purpose

Add getColorScale. Leverage this color scale to paint multiple lines in LineChart.

Change

  • Add getColorScale in graph package.
  • Paint multiple lines in line chart (given color encoding prop)

UI screenshot

  • nominal field as color

2019-02-26 6 35 48

  • quantitative field as color

2019-02-26 6 35 56

Risk

None.

TODO

[ ] Handle quantitative scaling properly so the lightest line in example in "quantitative field as color" could be clearer.

@chenesan chenesan self-assigned this Feb 26, 2019
fill={color}
/>
));
const graphGroup = dataGroup.map(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After adding color encoding the render function is larger and larger (more than 100~ lines now!). I'm not sure if there are better way to arrange them. Comment / idea are welcome =D.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm...it is indeed. But I cannot think of a way to deal with this neither. Maybe the <DataLine> could be extracted.

const fieldsToGroupBy: string[] = [color]
.filter(encoding => !!encoding)
.map(encoding => encoding.field);
const sortedData = data.sort((rowA, rowB) => xSelector.getOriginalVal(rowA) - xSelector.getOriginalVal(rowB));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found that if we don't sort it the lines will not be a path always from left to right..

@@ -88,6 +93,10 @@ export interface Theme {
colors: {
/** colors used for nominal data */
category: ReadonlyArray<string>;
sequential: {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@hsunpei hsunpei left a comment

Choose a reason for hiding this comment

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

Looks wonderful. Nice handlings with the colors scales and the data grouping functions 🎉✨
Some documentation may be further included.

@@ -29,7 +29,7 @@ export interface Scale {
/**
* Range of input channel
*/
range: [any, any];
range?: ReadonlyArray<any>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice use of ReadonlyArray 👍


import { getRecordFieldSelector } from './getRecordFieldSelector';

interface GetColorScaleArgs {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could it be named as ColorScaleArgs? Get as the initials sound like a function (though it is capitalized).

};
},
{},
));
Copy link
Contributor

Choose a reason for hiding this comment

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

How about using the map function of lodash?

const domain = _.map(data, 'field');

};
}
default: {
throw Error('Invalid color encoding type.');
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice error message 👍

case 'quantitative': {
const values = data.map(row => Number(row[field]));
const min = Math.min(...values);
const max = Math.max(...values);
Copy link
Contributor

Choose a reason for hiding this comment

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

How about utilizing the d3-array to handle calculations of the range?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea :)

* [{ type: 'a', color: 'green' }],
* [{ type: 'b', color: 'green' }],
* [{ type: 'a', color: 'red' }],
* [{ type: 'b', color: 'red' }],
Copy link
Contributor

Choose a reason for hiding this comment

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

Very nice documentation 👍
I was wondering if this example could be extended with another field (such as value: 1) so that the example could be easier to be understood at first glance.

* [{ type: 'b', color: 'red' }],
* ]
*/
export function getDataGroup(data: object[], fields: string[]): object[][] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be renamed as getGroupedDataByFields?

const defaultColor = theme.colors.category[0];
const getColor = colorScale ?
colorScale.selector.getScaledVal :
() => defaultColor;
Copy link
Contributor

Choose a reason for hiding this comment

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

You may follow the coding style of ternaries from airbnb:

const getColor = colorScale
  ? colorScale.selector.getScaledVal
  : () => defaultColor;

() => defaultColor;
const fieldsToGroupBy: string[] = [color]
.filter(encoding => !!encoding)
.map(encoding => encoding!.field);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this could be extracted as a function in utils

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for suggestion! I would extract a getDataGroupByEncodings since the fieldsToGroupBy is only used in building dataGroup.

fill={color}
/>
));
const graphGroup = dataGroup.map(
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm...it is indeed. But I cannot think of a way to deal with this neither. Maybe the <DataLine> could be extracted.

@chenesan chenesan force-pushed the refactor/axis-and-hover-hook branch from 1102a4b to 705a2af Compare March 4, 2019 03:20
@chenesan chenesan force-pushed the feature/color-scale branch from 1bc4867 to 5f7f4e5 Compare March 4, 2019 07:20
@chenesan
Copy link
Contributor Author

chenesan commented Mar 4, 2019

@garfieldduck Thanks for review! I have fix them and you can review again now :-)

data: object[];
}

function getNumericDomain(values: number[]): [number, number] {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

d3Extent return [number, number] | [undefined | undefined] so I have to filter out the undefined result in getNumericDomain

@chenesan chenesan changed the base branch from refactor/axis-and-hover-hook to develop March 4, 2019 07:32
@@ -69,10 +76,43 @@ const HoveringIndicator: FunctionComponent<{
);
};

const DataLine: React.FC<{
Copy link
Contributor

@zhusee2 zhusee2 Mar 4, 2019

Choose a reason for hiding this comment

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

I believe I saw this usage instead in #12? I'm not sure which one is correct though.

Suggested change
const DataLine: React.FC<{
const DataLine: React.FunctionComponent<{

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The source code of react type definition:

    type SFC<P = {}> = FunctionComponent<P>;

    /**
     * @deprecated as of recent React versions, function components can no
     * longer be considered 'stateless'. Please use `FunctionComponent` instead.
     *
     * @see [React Hooks](https://reactjs.org/docs/hooks-intro.html)
     */
    type StatelessComponent<P = {}> = FunctionComponent<P>;

    type FC<P = {}> = FunctionComponent<P>;

    interface FunctionComponent<P = {}> {
        (props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
        propTypes?: WeakValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }

So the FC and FunctionComponent should be the same thing. But indeed we had FunctionComponent in previous PR so I will make it consistent with that.

Also I read an article said that you shouldn't use FunctionComponent to type your function al component because they

  • define additional props like children and contextType, which may not be supported by your component.

  • break defaultProps resolution in typescript 3.1 (I'm stilly not fully understanding what that means in the article)

For now, I'm not sure if it's really that bad to have FunctionComponent type so I would just keep FunctionComponent here. Once it's clear that we should not use FC or FunctionComponent I may make a fix for this.

/** Width of the collision detection rectangle */
const color = theme.colors.category[0];
const bandWidth = graphWidth / (data.length - 1);
const colorScale = typeof color !== 'undefined' && getColorScale({
Copy link
Contributor

Choose a reason for hiding this comment

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

I'll suggest to wrap this expression to make it more prominent.

Suggested change
const colorScale = typeof color !== 'undefined' && getColorScale({
const colorScale = (typeof color !== 'undefined') && getColorScale({

return [
typeof extent[0] === 'undefined' ? 0 : extent[0],
typeof extent[1] === 'undefined' ? 0 : extent[1],
];
Copy link
Contributor

@zhusee2 zhusee2 Mar 4, 2019

Choose a reason for hiding this comment

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

Just some syntax sugar:

const [extentMin = 0, extentMax = 0] = d3.extent(values);

return [extentMin, extentMax];

Copy link
Contributor

@zhusee2 zhusee2 left a comment

Choose a reason for hiding this comment

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

Looks nice to me in general

Copy link
Contributor

@hsunpei hsunpei left a comment

Choose a reason for hiding this comment

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

Great work! Looks fantastic to me 💯 ✨

fill={color}
/>
));
return (<>
Copy link
Contributor

Choose a reason for hiding this comment

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

<> may be moved into a new line.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah nice catch
+1 with this

@chenesan chenesan merged commit 10eddb3 into develop Mar 5, 2019
@chenesan chenesan mentioned this pull request Mar 18, 2019
@zhusee2 zhusee2 deleted the feature/color-scale branch September 9, 2019 10:22
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