Skip to content

Commit

Permalink
Regions for lines
Browse files Browse the repository at this point in the history
  • Loading branch information
lcallarec committed Jan 23, 2024
1 parent b53b909 commit 3de537f
Show file tree
Hide file tree
Showing 19 changed files with 532 additions and 168 deletions.
35 changes: 33 additions & 2 deletions src/bezier.vala
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@

namespace LiveChart {
namespace LiveChart {
const double POLYNOMIAL_TOLERANCE = 1e-6;

public struct BezierCurve {
Coord c0;
Coord c1;
Coord c2;
Coord c3;
}

public BezierCurve build_bezier_curve_from_points(Point previous, Point target) {
var pressure = (target.x - previous.x) / 2.0;
BezierCurve bezier = {
c0: {
x: previous.x,
y: previous.y
},
c1: {
x: previous.x + pressure,
y: previous.y
},
c2: {
x: target.x - pressure,
y: target.y
},
c3: {
x: target.x,
y: target.y
}
};

return bezier;
}

//https://www.xarg.org/book/computer-graphics/line-segment-bezier-curve-intersection/
public Gee.List<Coord?> find_intersections_between(Segment segment, BezierCurve bezier) {
public Gee.List<Coord?> find_intersections_between_segment_and_curve(Segment segment, BezierCurve bezier) {

var ax = 3 * (bezier.c1.x - bezier.c2.x) + bezier.c3.x - bezier.c0.x;
var ay = 3 * (bezier.c1.y - bezier.c2.y) + bezier.c3.y - bezier.c0.y;
Expand Down
52 changes: 23 additions & 29 deletions src/geometry.vala
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,28 @@ namespace LiveChart {
Coord from;
Coord to;
}
public struct BezierCurve {
Coord c0;
Coord c1;
Coord c2;
Coord c3;
}

public BezierCurve build_bezier_curve_from_points(Point previous, Point target) {
var pressure = (target.x - previous.x) / 2.0;
BezierCurve bezier = {
c0: {
x: previous.x,
y: previous.y
},
c1: {
x: previous.x + pressure,
y: previous.y
},
c2: {
x: target.x - pressure,
y: target.y
},
c3: {
x: target.x,
y: target.y
}
};

return bezier;
}
//https://github.com/psalaets/line-intersect
public Coord? find_intersection_between_two_segments(Segment s1, Segment s2) {

var denom = ((s2.to.y - s2.from.y) * (s1.to.x - s1.from.x)) - ((s2.to.x - s2.from.x) * (s1.to.y - s1.from.y));
var numeA = ((s2.to.x - s2.from.x) * (s1.from.y - s2.from.y)) - ((s2.to.y - s2.from.y) * (s1.from.x - s2.from.x));
var numeB = ((s1.to.x - s1.from.x) * (s1.from.y - s2.from.y)) - ((s1.to.y - s1.from.y) * (s1.from.x - s2.from.x));

if (denom == 0 || ((numeA == 0 && numeB == 0))) {
return null;
}

var uA = numeA / denom;
var uB = numeB / denom;

if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
return {
x: s1.from.x + (uA * (s1.to.x - s1.from.x)),
y: s1.from.y + (uA * (s1.to.y - s1.from.y))
};
}

return null;
}
}
108 changes: 90 additions & 18 deletions src/line.vala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ using Cairo;
namespace LiveChart {
public class Line : SerieRenderer {

private LineDrawer drawer = new LineDrawer();
public Region? region {get; set; default = null; }

private LineSerieDrawer drawer = new LineSerieDrawer();

public Line(Values values = new Values()) {
base();
Expand All @@ -12,32 +14,102 @@ namespace LiveChart {

public override void draw(Context ctx, Config config) {
if (visible) {
drawer.draw(ctx, config, this.line, Points.create(values, config));
ctx.stroke();
drawer.draw(ctx, config, this.line, Points.create(values, config), this.region);
}
}
}

public class LineDrawer : Drawer {
public void draw(Context ctx, Config config, Path line, Points points) {
public class LineSerieDrawer : Drawer {
private LineDrawer drawer = new LineDrawer();
private RegionOnLineDrawer region_on_line_drawer = new RegionOnLineDrawer();
private LineIntersectionsGenerator intersections_generator = new LineIntersectionsGenerator();

public void draw(Context ctx, Config config, Path line, Points points, Region? region) {
if (points.size > 0) {
drawer.draw(ctx, config, line, points);
var segments = ctx.copy_path();


if(region != null && region.has_line_color()) {
ctx.push_group();
ctx.stroke();

ctx.set_operator(Operator.CLEAR);
region_on_line_drawer.draw(ctx, config, intersections_generator.generate(region, config, points));
ctx.fill();

ctx.pop_group_to_source();
ctx.paint();

ctx.push_group();

ctx.set_operator(Operator.SOURCE);
line.configure(ctx);
ctx.append_path(segments);
ctx.stroke();

ctx.set_operator(Operator.IN);
region_on_line_drawer.draw(ctx, config, intersections_generator.generate(region, config, points));
ctx.fill();
ctx.pop_group_to_source();
ctx.paint();

} else {
ctx.stroke();
}
}
}
}

line.configure(ctx);
private class LineIntersectionsGenerator : Drawer {
public Intersections generate(Region region, Config config, Points points) {
var resolver = new CurveRegionResolver(region);
var intersector = new SegmentIntersector(resolver, config);

for (int pos = 0; pos <= points.size -1; pos++) {
var previous_point = points.get(pos);
var target_point = points.after(pos);

if (this.is_out_of_area(previous_point)) {
continue;
}
intersector.intersect(previous_point, target_point, null);
}

return resolver.get_intersections();
}
}

private class LineDrawer : Drawer {
public void draw(Context ctx, Config config, Path line, Points points) {
line.configure(ctx);

var first_point = points.first();

ctx.move_to(first_point.x, first_point.y);
for (int pos = 0; pos < points.size -1; pos++) {
var current_point = points.get(pos);
var next_point = points.after(pos);
if (this.is_out_of_area(current_point)) {
ctx.move_to(current_point.x, current_point.y);
continue;
}

ctx.line_to(next_point.x, next_point.y);
var first_point = points.first();

ctx.move_to(first_point.x, first_point.y);
for (int pos = 0; pos < points.size -1; pos++) {
var current_point = points.get(pos);
var next_point = points.after(pos);
if (this.is_out_of_area(current_point)) {
ctx.move_to(current_point.x, current_point.y);
continue;
}

ctx.line_to(next_point.x, next_point.y);
}
}
}

private class RegionOnLineDrawer {
public void draw(Context ctx, Config config, Intersections intersections) {
var boundaries = config.boundaries();
intersections.foreach((intersection) => {
if (intersection != null) {
ctx.set_source_rgba(intersection.region.line_color.red, intersection.region.line_color.green, intersection.region.line_color.blue, intersection.region.line_color.alpha);
ctx.rectangle(intersection.start_x, boundaries.y.min, intersection.end_x - intersection.start_x, boundaries.height);
}
return true;
});
}
}
}
3 changes: 2 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ sources = files(
'path.vala',
'points.vala',
'region/region.vala',
'region/smooth_line_resolver.vala',
'region/curve_resolver.vala',
'region/intersections.vala',
'region/bezier_intersector.vala',
'region/segment_intersector.vala',
'serie.vala',
'serie_renderer.vala',
'series.vala',
Expand Down
14 changes: 7 additions & 7 deletions src/region/bezier_intersector.vala
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ namespace LiveChart {

public void intersect(Point previous, Point current, BezierCurve? curve) {
resolver.resolve(previous, current, (value) => {
return this.intersect_at(config, curve, value);
return this.intersect_at(curve, value);
});
}

private Coord? intersect_at(Config config, BezierCurve curve, double at_value) {
var intersection_segment = create_intersection_segment_at(config, at_value);
var intersection_coords = find_intersections_between(intersection_segment, curve);
private Coord? intersect_at(BezierCurve curve, double at_value) {
var intersection_segment = create_intersection_segment_at(at_value);
var intersection_coords = find_intersections_between_segment_and_curve(intersection_segment, curve);
if(intersection_coords.size > 0) {
return intersection_coords.first();
};
return null;
}

private Segment create_intersection_segment_at(Config config, double at_y) {
var boundaries = config.boundaries();
var y = boundaries.y.max - (at_y * config.y_axis.get_ratio());
private Segment create_intersection_segment_at(double at_value) {
var boundaries = this.config.boundaries();
var y = boundaries.y.max - (at_value * config.y_axis.get_ratio());
return {
from: {
x: boundaries.x.min,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ using Cairo;

namespace LiveChart {

public class SmoothLineRegionResolver : RegionResolver, Object {
private WaterlineRegionResolver resolver;
public class CurveRegionResolver : RegionResolver, Object {
private InOutWaterlinePoints points;
private Region region;
private Intersections intersections = new Intersections();

public SmoothLineRegionResolver(Region region) {
public CurveRegionResolver(Region region) {
this.region = region;
this.resolver = new WaterlineRegionResolver(region.floor, region.ceil);
this.points = new InOutWaterlinePoints(region.floor, region.ceil);
}

public Region get_region() {
Expand All @@ -21,9 +21,9 @@ namespace LiveChart {
}

public void resolve(Point previous, Point current, GetIntersection get_intersection) {
if (resolver.has_at_least_one_point_within(previous, current)) {
if (points.has_at_least_one_point_within(previous, current)) {

if (resolver.is_entering_by_the_bottom(previous, current)) {
if (points.is_entering_by_the_bottom(previous, current)) {

var entered_at = region.floor;
var coords = get_intersection(entered_at);
Expand All @@ -36,7 +36,7 @@ namespace LiveChart {
}
}
}
if (resolver.is_entering_by_the_top(previous, current)) {
if (points.is_entering_by_the_top(previous, current)) {

var entered_at = region.ceil;
var coords = get_intersection(entered_at);
Expand All @@ -50,7 +50,7 @@ namespace LiveChart {
}
}

if (resolver.is_leaving_by_the_top(previous, current)) {
if (points.is_leaving_by_the_top(previous, current)) {

var exited_at = region.ceil;
var coords = get_intersection(exited_at);
Expand All @@ -64,7 +64,7 @@ namespace LiveChart {
}
}
}
if (resolver.is_leaving_by_the_bottom(previous, current)) {
if (points.is_leaving_by_the_bottom(previous, current)) {

var exited_at = region.floor;
var coords = get_intersection(exited_at);
Expand All @@ -78,7 +78,7 @@ namespace LiveChart {
}
}
}
if(resolver.is_within(previous, current)) {
if (points.is_within(previous, current)) {

if(!intersections.has_an_opened_intersection()) {
intersections.open_without_entrypoint(region, previous.x);
Expand All @@ -90,11 +90,11 @@ namespace LiveChart {
}
}

public class WaterlineRegionResolver {
public class InOutWaterlinePoints {
private double floor;
private double ceil;

public WaterlineRegionResolver(double floor, double ceil) {
public InOutWaterlinePoints(double floor, double ceil) {
this.floor = floor;
this.ceil = ceil;
}
Expand Down
Loading

0 comments on commit 3de537f

Please sign in to comment.