You can do this in a few steps, basically using Geometry generator or Geometry by expression (see here for differences). The expression used for that starts with three variables that allow you to
- place the start point of the simplified line wherever you want,
- scale the length of the line continuously to fit your needs and,
- define the orientation (angle) of the line - like vertical, horizontal etc. in any direction.
Resulting blue line (representing river) and red buffer (census tracts); as you see, the selected parts of the river inside a census tract are highlighted in the simpified line as well, making it ease to identify:

River represented horizontally with variable @az = radians(90) and scaled 1:1 with variable @scale = 1:

A step by step procedure:
Convert census tracts from polygons to lines: Menu Vector > Geometry Tools > Polygons to lines
Split the river by the lines from step 1: Menu Processing > Toolbox > Split with lines
You now have a separate river feature for each census tract the river flows through.
Create an attribute called length for the length of each feature using Field calculator with the expression $length (assuming you use an appropriate CRS for measurements).
Based on this, calculate the accumulated length for each feature: the length from the first point of the river up to the last point of the current feature with this expression, calling the field accumulated
array_sum (
array_foreach (
generate_series(1,$id),
attribute (
get_feature_by_id (@layer,@element),
'length'
)
)
)
This step is optional, but if you want to create actual geometries (see below), you might want the result to have attributes representing the ones from the initial census tract. Create a new attribute census_tract to get the id of the respective census tract - you can then use this id for a table join with the initial census tract layer later. Use this expression: overlay_intersects ('census_tracts', $id,sort_by_intersection_size:='des')[0]
Now you can start to create your abstracted line. You basically need a point where to start the line and a direction. Let's say we simply use an arbitrary point, defined by it's x/y coordinates (in the local CRS I used): make_point (2564121,1182944) (line 1 in the expression below, change this value to fit your needs). The direction should be top-down, thus an azimuth of 180 degrees (change this in line 3).
Now we have everything together to create the abstract line from top to bottom, representing the length the river passes through each census tract. You can use Geometry Generator on the same layer (this is for visualizaiton purpose only. If you want actual geometries, see below). The idea is to create for each feature (river inside each tract) a vertical line with end-point at a distance corresponding to the acummulated value (step 4) and its start point at the acummulated value of the feature before. We then have to add the first tract manually, as it does not have a feature before it. I used an if clause for that.
To be easily able to adapt the length of the resulting line, I added a length scale factor in line 2 - changing this value, you can easily scale the lenght of the line. The third variable, az on line 3, allows you to rotate the orientation of the line - if you replace 180 by 90, you'll get a horizontal line instead of a vertical one. The whole expression then looks like this:
with_variable('start',make_point (2564121,1182944), -- change start point
with_variable('factor', 0.6, -- change length scale
with_variable('az', radians(180), -- rotate/change orientation of line
if (
$id=minimum($id),
make_line (
@start,
project(
@start,
attribute (
get_feature_by_id(@layer, minimum($id)),
'cumulated'
)*@factor,
@az
)
),
make_line (
project(
@start,
attribute (
get_feature_by_id (@layer,$id-1),
'cumulated'
)*@factor,
@az
),
project(
@start,
attribute (
get_feature_by_id (@layer,$id),
'cumulated'
)*@factor,
@az
)
)
))))
Now using another symbol layer of type Geometry generator, create a buffer around the line, symbolizing the census tracts the river intersects. Simply enclose the expression from above (abbreviated here as [expression]) in a buffer function: buffer ([expression], 1000, cap:='flat') - change the buffer distance of 1000 if necessary.
Creating actual geometries:
Using Geometry generator creates only a style for visualization. To create actual geometries, use Geometry by expression. For this, however, the expression must be modified a bit:
- instead of
@layer, you must use the layer's name in single quotes ', e.g. 'my_layer_name'. There are three spots where you must do this: on lines 11, 21 and 29.
- Replace
minimum($id) on lines 5 and 11 with the smallest $id value in your dataset. Normally, this should be 1. If unsure, simply type minimum($id) in the expression dialog window and see in the Previw what it returns, than manually insert this value in the expression.
The buffer (step 7) can now be done creating a simple buffer around the line. If you created the id in step 5, you can now make a table join to get the attributes from the initial census tracts layer.
The solution works well even when the river leaves a census tract an re-enters it, see example:
