codesuki / react-d3-components

D3 Components for React
http://codesuki.github.io/react-d3-components/example.html
MIT License
1.63k stars 206 forks source link

Area Chart with multiple y axis #110

Closed Kibo007 closed 6 years ago

Kibo007 commented 7 years ago

Is it possible to make with react-d3-components multiple y axis?

screen shot 2016-08-30 at 11 21 27
codesuki commented 7 years ago

Yes in general that's possible. While the cleaner approach is still in a branch.. you could copy paste and rename the current chart and add a new axis component inside. Then fix both axes settings. One to be on the left and one on the right side.

Kibo007 commented 7 years ago

if is not problem for you can you illustrate this in jsfiddle?

Would be from big help :)

codesuki commented 7 years ago

This is only a temporary way to do this but:

const MyCustomAreaChart = React.createClass({
    mixins: [
        DefaultPropsMixin,
        HeightWidthMixin,
        ArrayifyMixin,
        StackAccessorMixin,
        StackDataMixin,
        DefaultScalesMixin,
        TooltipMixin
    ],

    propTypes: {
        interpolate: string,
        stroke: func
    },

    getDefaultProps() {
        return {
            interpolate: 'linear',
            stroke: d3.scale.category20()
        };
    },

    _tooltipHtml(d, position) {
        const {x, y0, y, values} = this.props;
        const xScale = this._xScale;
        const yScale = this._yScale;

        const xValueCursor = xScale.invert(position[0]);

        const xBisector = d3.bisector(e => x(e)).right;
        let xIndex = xBisector(values(d[0]), xScale.invert(position[0]));
        xIndex = xIndex == values(d[0]).length ? xIndex - 1 : xIndex;

        const xIndexRight = xIndex == values(d[0]).length ? xIndex - 1 : xIndex;
        const xValueRight = x(values(d[0])[xIndexRight]);

        const xIndexLeft = xIndex == 0 ? xIndex : xIndex - 1;
        const xValueLeft = x(values(d[0])[xIndexLeft]);

        if (Math.abs(xValueCursor - xValueRight) < Math.abs(xValueCursor - xValueLeft)) {
            xIndex = xIndexRight;
        } else {
            xIndex = xIndexLeft;
        }

        const yValueCursor = yScale.invert(position[1]);

        const yBisector = d3.bisector(e => y0(values(e)[xIndex]) + y(values(e)[xIndex])).left;
        let yIndex = yBisector(d, yValueCursor);
        yIndex = yIndex == d.length ? yIndex - 1 : yIndex;

        const yValue = y(values(d[yIndex])[xIndex]);
        const yValueCumulative = y0(values(d[d.length - 1])[xIndex]) + y(values(d[d.length - 1])[xIndex]);

        const xValue = x(values(d[yIndex])[xIndex]);

        const xPos = xScale(xValue);
        const yPos = yScale(y0(values(d[yIndex])[xIndex]) + yValue);

        return [this.props.tooltipHtml(yValue, yValueCumulative, xValue), xPos, yPos];
    },

    render() {
        const {
            height,
            width,
            margin,
            colorScale,
            interpolate,
            stroke,
            values,
            label,
            x,
            y,
            y0,
            xAxis,
            yAxis
        } = this.props;

        const data = this._data;
        const innerWidth = this._innerWidth;
        const innerHeight = this._innerHeight;
        const xScale = this._xScale;
        const yScale = this._yScale;

        let line = d3.svg.line()
            .x(e => xScale(x(e)))
            .y(e => yScale(y0(e) + y(e)))
            .interpolate(interpolate);

        let area = d3.svg.area()
            .x(e => xScale(x(e)))
            .y0(e => yScale(yScale.domain()[0] + y0(e)))
            .y1(e => yScale(y0(e) + y(e)))
            .interpolate(interpolate);

        return (
            <div>
                <Chart height={height} width={width} margin={margin}>
                    <DataSet
                        data={data}
                        line={line}
                        area={area}
                        colorScale={colorScale}
                        stroke={stroke}
                        label={label}
                        values={values}
                        onMouseEnter={this.onMouseEnter}
                        onMouseLeave={this.onMouseLeave}
                    />
                    <Axis
                        className="x axis"
                        orientation="bottom"
                        scale={xScale}
                        height={innerHeight}
                        width={innerWidth}
                        {...xAxis}
                    />
                    <Axis
                        className="y axis"
                        orientation="left"
                        scale={yScale}
                        height={innerHeight}
                        width={innerWidth}
                        {...yAxis}
                    />
                    <Axis
                        className="y axis"
                        orientation="right"
                        scale={yScale}
                        height={innerHeight}
                        width={innerWidth}
                        {...yAxis}
                    />
                    {this.props.children}
                </Chart>
                <Tooltip {...this.state.tooltip}/>
            </div>
        );
    }
});

Notice there are 3 axes. You probably want to pass different scales to the "left" and "right" axes. That depends on your data.

Kibo007 commented 7 years ago

Thank you, I just add orientation param trough props and adjust your component to accept it

    React.createElement(Axis, _extends({
                    className: "y axis",
                    orientation: yOrientation,
                    scale: yScale,
                    height: innerHeight,
                    width: innerWidth
                }, yAxis)),

so than I rendered 2 times graph one with yAxis orientation left and once with different data set orientation right

<ResponsiveAreaChart
                    data={data}
                    height={400}
                    yOrientation="left"
                    yAxis={{zero: 0, outerTickSize: 0, tickPadding: 15, innerTickSize: 0, className: "yAxis"}}
                    xAxis={{outerTickSize: 0, tickPadding: 25, innerTickSize: 0, className: "xAxis", tickFormat: timeFormatMH}}
                    margin={{top: 10, bottom: 50, left: 50, right: 50}}/>
                </div>
                <div className={styles.y2Area}>
                  <ResponsiveAreaChart
                    data={data2}
                    height={400}
                    yOrientation="right"
                    xAxis={{outerTickSize: 0, tickPadding: 0, innerTickSize: 0, className: "xAxis", tickValues: null}}
                    yAxis={{outerTickSize: 0, tickPadding: 15, innerTickSize: 0, className: "yAxis", tickFormat: x => `${x}%`}}
                    margin={{top: 10, bottom: 50, left: 50, right: 50}}/>
                </div>

and than made them one above other so they look like

screen shot 2016-08-31 at 09 52 49
codesuki commented 7 years ago

That's another way to do it! Great you figured something out :)

Kibo007 commented 7 years ago

Would you like to accept this changes and merge them to master? I would just add possibility that people can change orientation instead that is hardcoded? Can make it to be default left but if you pass orientation than that one is used.

codesuki commented 7 years ago

Sure! Maybe it would be good to integrate it with the current axis property.

Kibo007 commented 7 years ago

Ok let me than prepare it and will make PR today.

Kibo007 commented 7 years ago

made PR https://github.com/codesuki/react-d3-components/pull/113/files

codesuki commented 7 years ago

Thank you :)

Kibo007 commented 7 years ago

So to use it from npm we need to wait your release? How often you are making releases?

codesuki commented 7 years ago

I just pushed the new 0.6.5 release to npm. Normally I try to release after each pullrequest. Btw it would be very nice if the change you made could be added to all other charts too. Maybe if you find the time.

Kibo007 commented 7 years ago

I will have to work soon on analytic section of our app so will need to use lot of others graphs so probably will need to do more changes on some components. I will do this changes you mention soon as I find time.

thnx

codesuki commented 7 years ago

Looking forward to your improvements!