JavaScript is required

Google Income Statement Ribbon Infographic

A standalone financial infographic viewer that renders a Google income statement as a left-to-right relation graph with amount-scaled node bars and proportional ribbon flows. It combines recursive RGJsonData preprocessing, custom node and line slots, and lightweight checked-state interactions into a compact final-state reference.

Google Income Statement Ribbon Infographic

What This Example Builds

This example builds a standalone financial infographic viewer for a Google income statement. The screen shows a left-to-right breakdown where revenue categories feed into a shared root and expense categories branch away from it, so the graph reads like a compact statement summary instead of a generic tree.

The most visible parts are data-scaled node columns, wide ribbon-like connections, branded callouts for selected Google business lines, and a layered gradient background. Users can inspect the flow bands through large clickable ribbon areas, and they can clear checked state by clicking empty canvas space.

Its main value is packaging. Compared with the related tutorial-style example, this version exposes only the finished viewer, so it is easier to study as a production-oriented reference.

How the Data Is Organized

The source data starts as one nested object with two top-level branches: revenues and expenses. Each item carries business fields such as name, amount, color, year_over_year_change, percentage_of_revenue, margin, and optional desc, while nested details arrays describe deeper breakdown levels.

Before setJsonData runs, getMyJsonData() converts that nested structure into RGJsonData with a synthetic root node, a flat nodes array, and a flat lines array. The recursive findNodesFromOrignData() helper also strips details out of each node payload so the remaining metrics can be rendered directly inside the node slot.

The preprocessing does more than flatten the tree. Revenue items are connected child -> parent, expense items are connected parent -> child, and every line stores percentage-based offset and height metadata in line.data. That extra geometry data is what lets the custom line slot render proportional ribbons instead of uniform strokes.

In a real application, the same structure could represent a profit and loss statement, a budget allocation tree, a category contribution chart, or any breakdown where both amount and flow proportion matter.

How relation-graph Is Used

The entry file keeps the composition minimal: RGProvider wraps StepFinalVersion, and the actual viewer component reads the graph instance through RGHooks.useGraphInstance(). That hook is central to the example because it is used both during graph initialization and inside the custom line slot.

The graph itself is configured as a left-to-right tree with large horizontal and vertical spacing. The options keep the structural defaults simple: rectangular base nodes, standard curved lines, left-right junction points, a 3px default line width, and a 1px default border width. Those defaults matter mostly as graph scaffolding because the visible presentation is overridden by slots and CSS.

RelationGraph binds two graph events: onLineClick and onCanvasClick. The local line handler only logs, so this example stays in viewer mode rather than opening editors or panels. The canvas handler is functional, though, because it calls clearChecked() to reset checked state.

The most important customization points are RGSlotOnNode and RGSlotOnLine. The node slot renders an HTML block whose height depends on node.data.amount, then stacks the node label, dollar amount, and optional percentage or year-over-year text above it. The line slot resolves the live link with getLinkByLine(), rebuilds an SVG area path with createAreaLinePathWithOffset(), and forwards clicks back into relation-graph through graphInstance.onLineClick(...).

The stylesheet completes the effect. It replaces the plain canvas background with fixed radial gradients, removes visible node borders, adds a halo for checked nodes, and makes the custom ribbon paths react to hover and checked state by increasing opacity. Together, those overrides turn a standard tree layout into a presentation-focused infographic.

Key Interactions

The graph initializes on mount. initializeGraph() loads transformed JSON data, centers the graph, fits it to the viewport, and then zooms out slightly so the full composition has more breathing room.

Line inspection is implemented through the custom ribbon body rather than through thin default strokes. Each filled SVG area captures clicks on its whole surface and forwards that event into relation-graph’s built-in line-click handling, which makes wide financial flows easier to inspect.

Canvas clicks act as a reset. Clicking empty space clears checked state, which removes the active visual emphasis from graph items.

The interaction model remains intentionally light. The example does not add editing, drill-down, or side-panel inspection. Even the application-level onLineClick handler only logs the clicked line, so the focus stays on graph presentation and checked-state feedback.

Key Code Fragments

This fragment shows that the standalone example is only a provider wrapper around the finished implementation.

const Example: React.FC = () => {

    return (
        <RGProvider><StepFinalVersion /></RGProvider>
    );
};

This fragment shows the left-to-right tree baseline and the graph defaults that support the custom rendering layer.

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'tree',
        from: 'left',
        treeNodeGapH: 300,
        treeNodeGapV: 100
    },
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardCurve,
    defaultLineWidth: 3,
    defaultJunctionPoint: RGJunctionPoint.lr,
    defaultNodeBorderWidth: 1
};

This fragment shows the mount-time data load and viewport adjustment.

const initializeGraph = async () => {
    const myJsonData = await getMyJsonData();
    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
    graphInstance.zoom(-10); // Zoom out by 10% based on current zoom level
};

This fragment proves that revenue lines are stored as child-to-parent ribbons with percentage-based offsets.

if (income) {
    allJsonLines.push({
        from: cJsonNode.id,
        to: parentJsonNode.id,
        color: cJsonNode.color,
        data: {
            fromOffsetYPercent: 0,
            fromHeightPercent: 1,
            toOffsetYPercent: sumAmount / parentJsonNode.data.amount,
            toHeightPercent: cJsonNode.data.amount / parentJsonNode.data.amount,
        }
    });
}

This fragment proves that expense lines reverse the direction while keeping the same proportional geometry idea.

allJsonLines.push({
    from: parentJsonNode.id,
    to: cJsonNode.id,
    color: cJsonNode.color,
    data: {
        fromOffsetYPercent: sumAmount / parentJsonNode.data.amount,
        fromHeightPercent: cJsonNode.data.amount / parentJsonNode.data.amount,
        toOffsetYPercent: 0,
        toHeightPercent: 1,
    }
});

This fragment shows the amount-driven node markup and the stacked financial labels.

<div style={{width: '40px', height: `${(node.data?.amount / 100) * 200}px`, position: 'relative'}}>
    <div style={{
        position: 'absolute',
        top: '0px',
        left: '0px',
        color: '#0c63ff',
        width: '200px',
        whiteSpace: 'nowrap',
        textAlign: 'left'
    }}>
        <div style={{transform: 'translateY(-110%)'}}>
            <div style={{fontSize: '24px'}}>{node.text}</div>
            <div style={{fontSize: '22px'}}>$ {node.data.amount} B</div>

This fragment shows how the custom line slot rebuilds ribbon geometry from live link positions and keeps the line clickable.

const graphInstance = RGHooks.useGraphInstance();
const link = graphInstance.getLinkByLine(lineConfig.line)!;
const path = createAreaLinePathWithOffset(
    link.fromNode,
    link.toNode,
    lineConfig.line,
    1
);

const onClick = (e) => {
    graphInstance.onLineClick(lineConfig.line, e);
};

This fragment shows that the final presentation also depends on CSS, not only on graph options.

.rg-node-peel.rg-node-checked {
    .rg-node {
        border: none;
        box-shadow: 0 0 0 10px var(--rg-node-color);
    }
}

.my-rg-line {
    opacity: 0.5;
    pointer-events: fill;
    cursor: pointer;

What Makes This Example Distinct

The comparison data places this example closest to demo-for-google-income-statement, but the difference is important: this file set removes the six-step tutorial shell and keeps only the finished viewer. That makes it a better starting point when the goal is to reuse the completed infographic pattern rather than teach the build-up process.

Its rare implementation pattern is the bidirectional preprocessing around one synthetic root. Revenue branches point inward, expense branches point outward, and both sides keep percentage metadata that the line slot later turns into proportional ribbons. That is a more specialized pattern than the ordinary left-to-right tree scaffolding shared with many other examples.

Compared with node-content-lines, the distinctive lesson is not connector targeting inside rich node content. Here the stronger idea is custom edge rendering: the example stores geometric proportions during preprocessing and recomputes filled flow bands from live node positions at render time.

Compared with canvas-event and customize-fullscreen-action, this example uses a similar hook-initialized viewer baseline for a different purpose. Those examples emphasize event instrumentation or surrounding page behavior, while this one concentrates on a polished financial narrative with amount-scaled columns, ribbon flows, branded callouts, checked halos, and a gradient canvas.

Where Else This Pattern Applies

This pattern transfers well to other statement-style views where one dataset needs both hierarchy and proportional flow. Examples include operating-cost breakdowns, departmental budget allocation, business-unit contribution charts, and sustainability or resource-flow summaries.

It also fits dashboards that need a polished narrative graphic rather than an editing surface. Teams can keep the same recursive preprocessing and line-geometry approach while replacing the Google-specific labels, colors, logos, and explanatory text with their own business domains.