react-native-chart icon indicating copy to clipboard operation
react-native-chart copied to clipboard

Multiline

Open Mindaugas-Jacionis opened this issue 8 years ago • 17 comments

I know this is in your todo list, but I was just wondering how is the progress regarding multiline graph?

Mindaugas-Jacionis avatar Aug 22 '16 07:08 Mindaugas-Jacionis

I (sort-of) found a solution to this - but had to hack the hell out of it.

Go into LineChart.js and adjust the render to use a drawLines with two data sets (or more) in your data array. It's messy and buggy - but this approach works.

 _drawLines = () => {

            var lines = [];


            for(var lineData in this.props.data) {
                lines.push(this._drawLine(this.props.data[lineData], this.props.dataColours[lineData]));
            }

            return lines;

    };

    render() : any {


        return (
            <View>
                <Grid {...this.props} />
                <Animated.View style={{ opacity: this.state.opacity, backgroundColor: 'transparent' }}>
                    {this._drawLines()}
                </Animated.View>
            </View>
        );
    }


What I don't entirely understand is why the first point is being done independently from the others.

bfarrgaynor avatar Aug 26 '16 19:08 bfarrgaynor

Using that approach I was able to get this:

screenshot 2016-08-26 15 10 54

But had to hack in support for my own line colours. With a colours array.

_drawLine = (lineData, colour) => {  

...

bfarrgaynor avatar Aug 26 '16 19:08 bfarrgaynor

would you mind sharing the LineChart.js class ?

ideaCompany avatar Sep 19 '16 11:09 ideaCompany

Yes will post later today when I get a moment.

bfarrgaynor avatar Sep 19 '16 11:09 bfarrgaynor

@bfarrgaynor, i've tried your solution. But lines are not appeared at all. For some reason array of elements (lines) simply was not rendered. Any ideas y?

madpau1 avatar Sep 29 '16 10:09 madpau1

/* @flow */
import React, { Component } from 'react';
import { Animated, ART, View } from 'react-native';
const { Surface, Shape, Path } = ART;
// import Morph from 'art/morph/path';
import * as C from './constants';
import Circle from './Circle';
const AnimatedShape = Animated.createAnimatedComponent(Shape);
import Grid from './Grid';

var makeDataPoint = (x : number, y : number, data : any, myDataPointColor : any) => {
    return { x, y, radius: data.dataPointRadius, fill: myDataPointColor, stroke: myDataPointColor };
};

var calculateDivisor = (minBound : number, maxBound : number) : number => {
    return (maxBound - minBound <= 0) ? 0.00001 : maxBound - minBound;
};

export default class LineChart extends Component<void, any, any> {

    constructor(props : any) {
        super(props);
        this.state = { opacity: new Animated.Value(0) };
    }

    componentWillUpdate() {
        Animated.timing(this.state.opacity, { duration: 0, toValue: 0 }).start();
    }

    componentDidUpdate() {
        Animated.timing(this.state.opacity, { duration: 500, toValue: 1 }).start();
    }

    _drawLine = (lineData, colour) => {


        containerHeight = this.props.height;
        containerWidth = this.props.width;
        const data = lineData || [];

        let minBound = this.props.minVerticalBound;
        let maxBound = this.props.maxVerticalBound;



        // For all same values, create a range anyway
        if (minBound === maxBound) {
            minBound -= this.props.verticalGridStep;
            maxBound += this.props.verticalGridStep;
        }



        const divisor = calculateDivisor(minBound, maxBound);
        let scale = (containerHeight + 1) / divisor;

        const horizontalStep = containerWidth / data.length;
        var dataPoints = [];
        const firstDataPoint = data[0][1];

        //if(minBound == undefined || isNaN(minBound)) { minBound = 0; }
        minBound = (firstDataPoint * -1);
        //if(scale == undefined || isNaN(scale)) { scale = 18.2; }
        scale = (containerHeight / (maxBound - 1));

        if(containerHeight == undefined || isNaN(containerHeight)) { containerHeight = 181; }


        /*
        console.log("divisor: " + divisor);
        console.log("minBound: " + minBound);
        console.log("scale: " + scale);
        console.log("containerHeight: " + containerHeight);
        console.log("firstDataPoint: " + firstDataPoint);
        */


        //if(firstDataPoint == undefined) { firstDataPoint = 5 }

        var height = (containerHeight - (firstDataPoint * scale)) + 10;


        //console.log("(" + containerHeight + " - (" + firstDataPoint + " * " + scale + "))");

        //console.log("Source height is: " + height);


        if(height < 22) { //too low
            //console.log("CONDITION AA");
            height = 22;    
        }

        if(height > 180) { //too high
           // console.log("CONDITION BB");
            height = 175;   
        }



        //console.log("FIRST HEIGHT: " + height)
        //console.log("CONTAINER HEIGHT: " + containerHeight)

        const path = new Path().moveTo(15, height-10);
        const fillPath = new Path().moveTo(15, height).lineTo(0, height);

        dataPoints.push(makeDataPoint(15, height-10, this.props, colour));

        data.slice(1).forEach(([_, dataPoint], i) => {



            let _height = (containerHeight - (dataPoint * scale));

            //console.log("Datapoint is: " + dataPoint + " height will be: " + _height);

            if (_height < 0) {
                //console.log("Setting height to 0");
                 _height = 0;
            }

            const x = horizontalStep * (i) + (horizontalStep+12);
            var y = Math.round(_height) + 12;

            //console.log("Y was: " + y);

            if(y < 0) { //too high
                //console.log("CONDITION A");
                y = 12; 
            }

            if(y > 180) { //too low
                 //console.log("CONDITION B");
               y = 175; 
            }

            //console.log("Y is now: " + y);

            path.lineTo(x, y);
            fillPath.lineTo(x, y);
            dataPoints.push(makeDataPoint(x, y, this.props, colour));
        });
        fillPath.lineTo(dataPoints[dataPoints.length - 1].x, containerHeight);
        if (this.props.fillColor) {
            fillPath.moveTo(0, containerHeight);
        }
        if (path.path.some(isNaN)) return null;
        return (
            <View>
                <View style={{ position: 'absolute' }}>
                    <Surface width={containerWidth} height={containerHeight}>
                        <AnimatedShape d={path} stroke={colour || C.BLUE} strokeWidth={this.props.lineWidth} />
                        <AnimatedShape d={fillPath} fill={this.props.fillColor} />
                    </Surface>
                </View>
                <View style={{ position: 'absolute' }}>
                    <Surface width={containerWidth} height={containerHeight} />
                </View>
                {(() => {
                    if (!this.props.showDataPoint) return null;

                    //console.log(dataPoints);
                    return (
                        <View style={{ position: 'absolute',}}>
                        <Surface width={containerWidth} height={containerHeight + 10}>
                            {dataPoints.map((d, i) => <Circle key={i} {...d} />)}
                        </Surface>
                        </View>
                    );
                })()}
            </View>
        );
    };

     _drawLines = () => {

            var lines = [];


            for(var lineData in this.props.data) {
                lines.push(this._drawLine(this.props.data[lineData], this.props.dataColours[lineData]));
            }

            return lines;

    };

    render() : any {


        return (
            <View>
                <Grid {...this.props} />
                <Animated.View style={{ opacity: this.state.opacity, backgroundColor: 'transparent' }}>
                    {this._drawLines()}
                </Animated.View>
            </View>
        );
    }
}

bfarrgaynor avatar Sep 29 '16 11:09 bfarrgaynor

I basically made data accept an array of arrays in the params for the component.

From there, I looped through the param array of data like this:

 for(var lineData in this.props.data) {
                lines.push(this._drawLine(this.props.data[lineData], this.props.dataColours[lineData]));
            }

It's very hacky and I seem to be having quirks and issues with the first element in the list as you can see from my tracing, it also doesn't scale the x & y axis' perfectly. But it's a start and it does 'work'. Not good enough for a PR.

bfarrgaynor avatar Sep 29 '16 11:09 bfarrgaynor

'data': [
                                    [
                                        ["Mon", 35],
                                        ["Tue", 35],
                                        ["Wed", 10],
                                        ["Thu", 30],
                                        ["Fri", 15],
                                        ["Sat", 15],
                                        ["Sun", 25],
                                    ],

                                    [
                                        ["Mon", 30],
                                        ["Tue", 10],
                                        ["Wed", 15],
                                        ["Thu", 16],
                                        ["Fri", 18],
                                        ["Sat", 30],
                                        ["Sun", 15],
                                    ]
                                ]
                        },
  dataColours:['#F6A800','#8E8E92'],

... in my render function:

<Chart
                            style={{margin:10, width:(window.width-35), height:200}}
                            lineWidth={3}
                            data={this.state.data}
                            showDataPoint={true}
                            dataPointFillColor={'black'}
                            tightBounds={false}
                            dataPointRadius={5}
                            dataColours={this.state.dataColours}
                            gridColor={'#CCCCCC'}
                            hideVerticalGridLines={true}
                            verticalGridStep={1}
                            minVerticalBound={0}

                            type="line"
                         />   

bfarrgaynor avatar Sep 29 '16 11:09 bfarrgaynor

Not having any success with this - any news?

morelazers avatar Oct 23 '16 16:10 morelazers

I have many Warning asWarning: Component's children should not be mutated. in Text (at yAxis.js:54) in RCTView (created by View) in View (at yAxis.js:73) in YAxis (at Chart.js:164) in RCTView (created by View) in View (at Chart.js:163) in RCTView (created by View) in View (at Chart.js:162) in RCTView (created by View) in View (at Chart.js:157) in RCTView (created by View) in View (at Chart.js:152) in Chart (created by ChartView) in RCTView (created by View) in View (created by ChartView) in ChartView (created by HistoryView) in RCTView (created by View) in View (created by HistoryView) in HistoryView (created by TYRCTApp) in RCTView (created by View) in View (created by TYRCTApp) in TYRCTApp in RCTView (created by View) in View (created by AppContainer) in RCTView (created by View) in View (created by AppContainer) in AppContainer this is my paramsdata: [ [ [ '10:20', 1 ], [ '10:40', 3 ], [ '11:00', 7 ], [ '11:20', 9 ], [ '11:40', 10 ], [ '12:00', 10 ], [ '12:20', 10 ] ], [ [ '10:20', 3 ], [ '10:40', 6 ], [ '11:00', 10 ], [ '11:20', 9 ], [ '11:40', 5 ], [ '12:00', 8 ], [ '12:20', 10 ] ] ], verticalGridStep: 5, type: 'line', showDataPoint: false, axisColor: '#2B2B2B', axisLabelColor: '#FFFFFF', lineWidth: 2, yAxisShortLabel: true, dataColours: [ '#21b162', '#424242' ], animated: true,…… Where there is a problem?thanks @bfarrgaynor

zhaixiaoou avatar Oct 26 '16 11:10 zhaixiaoou

Any updates on this?

xBATx avatar Nov 20 '16 20:11 xBATx

Hi Guys, I got multi-line half sort-of working with the code I provided. But I gave up on it after a while because it was clear it wasn't going to be reliable. I had shifting happening all over the place with different data inputs.

bfarrgaynor avatar Nov 21 '16 18:11 bfarrgaynor

Hey guys, I managed to get this working in the end, it works good enough for me but there are still some major problems regarding offsets on the x axis. Here's the code I used:

chartData = [[[0, 1], [1, 2], [2, 3], [3, 4]], [[2, 5], [3, 6], [7, 2]]];
<Chart
    data={chartData}
    verticalGridStep={5}
    type="line"
    showDataPoint={true}
    color={["blue", "red"]}
/>

I am using the latest code from the repo (NOTE: NOT NPM).

Regarding the x axis, it's not adding points in the right places. They all start from the same place and track along at the same rate as the first as you will see if you use the code I provided.

I'm not sure exactly how to solve this, and I don't have the time right now to dig into the class, but for anyone looking I think a good starting point is line 87 in LineChart.js

morelazers avatar Nov 22 '16 11:11 morelazers

The first point is treated distinctly from the rest in the series and I feel like that whole thing should be thrown out. All points should go through the same process.

bfarrgaynor avatar Nov 22 '16 12:11 bfarrgaynor

I thought that this functionality had already been implemented and added to npm as well. Thanks for suggestions Guys, I'll try it.

xBATx avatar Nov 24 '16 09:11 xBATx

As a generic FYI — I am no longer able to maintain this library. I recommend checking out victory-native as it's much more maintained.

tomauty avatar Jul 07 '17 20:07 tomauty

screen shot 2017-10-03 at 10 59 48 pm I would like to draw multi line graph. Please check and assist and let me know if we can able to draw graph with this library.

ManikSmartdata avatar Oct 03 '17 17:10 ManikSmartdata