Skip to content
On this page

Riemann sums

This is one of the more complex examples. It draws Riemann partitions from point a to point b. While computing the partitions, their areas are summed up to show how the Riemann approximation compares to the true area under the given curve.

In this example, some extra markup is used outside of Mafs to provide some inputs into the Mafs visualization. This is common, and recommended! Movable points are not the only way to provide inputs to Mafs.

Partitions:
Code
vue
<script setup lang="ts">
import { Mafs, Cartesian, OfX, Polygon, Text, MovablePoint, useMovablePoint, AlignType, CardinalDirection } from 'mafs-vue'
import { computed, ref } from 'vue'
import sumBy from "lodash/sumBy"
import range from "lodash/range"

interface Partition {
    polygon: [number, number][]
    area: number
}

const maxNumPartitions = 200
// Inputs
const numPartitions = ref(40)
const lift = useMovablePoint([0, -1], {
    constrain: AlignType.VERTICAL
})
const a = useMovablePoint([1, 0], {
    constrain: AlignType.HORIZONGTAL
})
const b = useMovablePoint([11, 0], {
    constrain: AlignType.HORIZONGTAL
})
// The function
const wave = (x: number) =>
    Math.sin(3 * x) + x ** 2 / 20 - 2 + lift.point[1] + 2
const integral = (x: number) =>
    (1 / 60) * (x ** 3 - 20 * Math.cos(3 * x)) + lift.point[1] * x
// Outputs
const exactArea = computed<number>(() => {
    return integral(b.point[0]) - integral(a.point[0])
})
const partitions = computed<Partition[]>(() => {
    const dx = (b.point[0] - a.point[0]) / numPartitions.value
    return range(a.point[0], b.point[0] - dx / 2, dx).
        map((x) => {
            const yMid = wave(x + dx / 2)
            return {
                polygon: [
                    [x, 0],
                    [x, yMid],
                    [x + dx, yMid],
                    [x + dx, 0],
                ],
                area: dx * yMid,
            }
        })
})
const areaApprox = computed<number>(() => sumBy(partitions.value, "area"))
const areaColor = (area: number) => {
    return (area >= 0) ? "hsl(112, 100%, 47%)" : "hsl(0, 100%, 47%)"
}
</script>

<template>
    <div>
        <Mafs :height="400" :viewBox="{ x: [-1, 12], y: [-3, 10] }">
            <Cartesian :subdivisions="2" />
            <OfX :y="wave" :stroked="{ color: '#358CF1' }" />
            <Polygon v-for="({ polygon, area }, index) in partitions" :key="index" :points="polygon"
                :filled="{ fillOpacity: numPartitions / maxNumPartitions, color: areaColor(area) }" />
            <Text :attach="CardinalDirection.E" :x="1.2" :y="5.5" :size="20">
                Midpoint Riemann sum:
            </Text>
            <Text :attach="CardinalDirection.E" :x="1.2" :y="4.5" :size="30">
                {{ areaApprox.toFixed(4) }}
            </Text>
            <Text :attach="CardinalDirection.E" :x="1.2" :y="3.5" :size="20">
                True area:
            </Text>
            <Text :attach="CardinalDirection.E" :x="1.2" :y="2.5" :size="30">
                {{ exactArea.toFixed(4) }}
            </Text>
            <MovablePoint :ctx="lift" />
            <MovablePoint :ctx="a" />
            <MovablePoint :ctx="b" />
        </Mafs>
        <div class="range">
            Partitions:{{ " " }}
            <input type="range" :min="20" :max="200" v-model="numPartitions" />
        </div>
    </div>
</template>

<style scoped>
.range {
    padding: 1rem;
    border-color: #374151;
    border-top-width: 1px;
    background-color: black;
    color: white;
}
</style>