margelo / react-native-graph

📈 Beautiful, high-performance Graphs and Charts for React Native built with Skia
https://margelo.io
MIT License
2.08k stars 118 forks source link

Q: Adding Linear Gradient to fill Path area #11

Closed vladyslavNiemtsev closed 2 years ago

vladyslavNiemtsev commented 2 years ago

Hi! I tried to implement LinearGradient to fill out all area under Path line.

Wanted to get something like this:

image

Do you know how it could be implemented with react-native-skia LinearGradient component?

I can't get the needed behaviour.. :(

vladyslavNiemtsev commented 2 years ago

With plain SVG it could be implemented with following code:

<Defs> <LinearGradient id={gradient`} x1="0" x2="0" y1="0" y2="100%"

`

vladyslavNiemtsev commented 2 years ago

When I tried to implement something similar

image

I get:

image
vladyslavNiemtsev commented 2 years ago

I suppose that for me I will need to create slightly different shape to give it just the same shape of my figure that I want to paint. (the shape under my path line)

DDushkin commented 2 years ago

I suppose that for me I will need to create slightly different shape to give it just the same shape of my figure that I want to paint. (the shape under my path line)

I can send a solution in an hour, or meanwhile you can check the generateGraph function in the fork I created

DDushkin commented 2 years ago

The idea is that for filling the path with gradient it should be closed and the current path isn't

vladyslavNiemtsev commented 2 years ago

It will be so great, thanks! @DDushkin

In D3 it solves with areas: https://github.com/d3/d3-shape/blob/v3.1.0/README.md#areas

So, I could just walk throw my data array and specify the same curve function as it was for my Path creating, the example is here:

image

So, in this case the D3 will understand the algorithm for curve function and could understand the correct area for this Path.

But in case of Skia Path creation, I don't understand yet how I could do this.

It will be very helpful if you'll provide your solution!

DDushkin commented 2 years ago

So Skia creates SVG path with commands the same way as you would create an SVG path manually with flags like M 0 100 L 100,100 z and etc. So here looking to your screenshot imagine the first and last point of your path and think how to make that path looped.

//./src/CreateGraphPath.ts

const path = Skia.Path.Make(); // -> initialize SVG path

  for (let i = 0; i < points.length; i++) {
    const point = points[i]!;

    // first point needs to start the path
    if (i === 0) path.moveTo(point.x, point.y); //-> start point

    const prev = points[i - 1];
    const prevPrev = points[i - 2];

    if (prev == null) continue;

    const p0 = prevPrev ?? prev;
    const p1 = prev;
    const cp1x = (2 * p0.x + p1.x) / 3;
    const cp1y = (2 * p0.y + p1.y) / 3;
    const cp2x = (p0.x + 2 * p1.x) / 3;
    const cp2y = (p0.y + 2 * p1.y) / 3;
    const cp3x = (p0.x + 4 * p1.x + point.x) / 6;
    const cp3y = (p0.y + 4 * p1.y + point.y) / 6;

    path.cubicTo(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y); 
    if (i === points.length - 1) {
      path.cubicTo(point.x, point.y, point.x, point.y, 4 * point.x, point.y); //-> end point
    }
  }
  /* 
  At this point, we generated LineGraph,
  but to build the Gradient we need to connect the first and last points 
  */
  path.lineTo(width + graphPadding, height + graphPadding); //-> go to right bottom corner
  path.lineTo(0 + graphPadding, height + graphPadding); //-> go to left bottom corner

  /*
  Now we kinda have the correct shape of our gradient
  */
  return path;

Then in the render method, we add another Path with fill near our current Path with stroke

              <Path
                path={path}
                strokeWidth={lineThickness}
                color={color}
                style="stroke"
                strokeJoin="round"
                strokeCap="round"
              />
              {gradientColors && ( //-> New one
                <Path path={path}>
                  <LinearGradient
                    start={vec(0, 0)}
                    end={vec(0, height)}
                    colors={gradientColors} //as Color[]
                  />
                </Path>
              )}

This is how I solved the issue, maybe @mrousavy has a better solution?

vladyslavNiemtsev commented 2 years ago

@DDushkin Yeah, got it, thanks for your solution - it works for me!

mrousavy commented 2 years ago

Ah yea the solution isn't that difficult, you just need to reverse the Path then it works 😄

Leoputera2407 commented 2 years ago

Hey @mrousavy, what do you mean by reverse the Path?

chrispader commented 2 years ago

This was implemented in #41 and is available now in versions above 0.3.0