 # Calculate area under graph

Is there a way to calculate the area under the graph for user input start and end? And also have the varea color from selected values. I tried something below, but not sure how to have the code only iterate between start and end. Also not entirely sure how to change the colored area under the graph. Anyone have a clue?

``````import numpy as np
from bokeh.models import ColumnDataSource, Grid, LinearAxis, Plot, VArea, TextInput, CustomJS
from bokeh.layouts import column, row
from bokeh.plotting import show, figure

t = np.linspace(1, 101, 100)
A, n = 1, -0.65
y1 = np.zeros(100)
y2 = A*np.power(t, n)
source = ColumnDataSource(dict(t=t, y1=y1, y2=y2))

start = TextInput(value="0", title="Start", width = 200)
end = TextInput(value="100", title="End", width = 200)

callback = CustomJS(args=dict(source=source,
start = start,
end = end), code="""
var data = source.data
var t1 = start.value
var t2 = end.value

const closest = data.t.reduce((a, b) => {
return Math.abs(b - t1) < Math.abs(a - t1) ? b : a;
});
console.log(closest);

var area = 0;
var height = 1;
console.log(t1, t2)
for (var i = 0; i < (data.t.length - 1); i++) {
console.log(data.y2[i+1], data.y2[i], data['y2'][i], 0.5 * (data.y2[i + 1] + data.y2[i]) * height)
area += 0.5 * (data.y2[i + 1] + data.y2[i]) * height;
}
console.log(area)

source.change.emit()
""")

start.js_on_change('value', callback)
end.js_on_change('value', callback)

plot = figure(title=None, plot_width=900, plot_height=300)

plot.varea(x="t", y1="y1", y2="y2", fill_color="#f46d43", source = source)

show(row(plot, column(start, end)))
``````

Regarding finding start and end indices. You can either interpolate to get more precise values or you can just find the closest indices, like you do in your code (didn’t check if it works):

``````    var {data} = source;

// Can be made faster if `data.t` is sorted and you use binary search.
const find_closest_idx = (val) => {
return data.t.reduce(({delta, idx}, curr, curr_idx) => {
const curr_delta = Math.abs(curr - val);
return (delta === undefined || curr_delta < delta) ? {delta: curr_delta, idx: curr_idx} : {delta, idx};
}, {}).idx;
}

const start_idx = find_closest_idx(start.value);
const end_idx = find_closest_idx(end.value);
``````

Now you can just iterate from `start_idx` to `end_idx` and get the relevant data points. Make sure that your code works if the indices are the same.

About computing the area - that’s just basic geometry, nothing special about it.
For each pair of consecutive indices, you have four points that define a trapezoid. Trapezoid area is easy to find. All that’s needed is to find areas of all trapezoids and combine them.

Regarding the color - I don’t see anything in your code that would change the color. And I feel like we’ve discussed it before. But maybe it was someone else. Either way, just save the result of `plot.varea`, pass it to the relevant `CustomJS` callback and then just set its `.glyph.fill_color` property.

1 Like

Hi p-himik,

Thanks for the help! (added the updated code below).
Regarding the color I meant that the area shown in the graph should only be colored from start to end values. Like this: (if start and end is 2, 4) ``````import numpy as np
from bokeh.models import ColumnDataSource, Grid, LinearAxis, Plot, VArea, TextInput, CustomJS
from bokeh.layouts import column, row
from bokeh.plotting import show, figure
from bokeh.io import output_file

output_file('areaundergraph.html')

t = np.linspace(1, 101, 1000)
A, n = 1, -0.65
y1 = np.zeros(1000)
y2 = np.zeros(1000) + 5# A*np.power(t, n)
source = ColumnDataSource(dict(t=t, y1=y1, y2=y2))

start = TextInput(value="0", title="Start", width = 200)
end = TextInput(value="100", title="End", width = 200)

plot = figure(title=None, plot_width=900, plot_height=300)
Varea = plot.varea(x="t", y1="y1", y2="y2", fill_color="#f46d43", source = source)

callback = CustomJS(args=dict(source=source,
Varea = Varea,
start = start,
end = end), code="""
var data = source.data

console.log(Varea)

// Can be made faster if `data.t` is sorted and you use binary search.
const find_closest_idx = (val) => {
return data.t.reduce(({delta, idx}, curr, curr_idx) => {
const curr_delta = Math.abs(curr - val);
return (delta === undefined || curr_delta < delta) ? {delta: curr_delta, idx: curr_idx} : {delta, idx};
}, {}).idx;
}

const start_idx = find_closest_idx(start.value);
const end_idx = find_closest_idx(end.value);

var area = 0;
var height = 0.1;
for (var i = start_idx; i <= (end_idx); i++) {
//console.log(i, i+1)
//area += 0.5 * (data.y2[i] + data.y2[i-1]);
area += 0.5 * (data.y2[i] + data.y2[i-1]) * height;

}
console.log(area)

source.change.emit()
""")

start.js_on_change('value', callback)
end.js_on_change('value', callback)

show(row(plot, column(start, end)))
``````

You will have to add another glyph that spans just from start to end for such coloring - `varea`'s `fill_color` is not vectorized, so its value is applied to the whole glyph.

Hei,

Thanks for the help! If anyone was curious about the solution I added it below, or if you have any input to make it more elegant and faster Just need to figure out how to have it plot the area as text either outside the plot below the Textinput, or maybe as a title.

``````import numpy as np
from bokeh.models import ColumnDataSource, Grid, LinearAxis, Plot, VArea, TextInput, CustomJS, Band
from bokeh.layouts import column, row
from bokeh.plotting import show, figure
from bokeh.io import output_file

output_file('areaundergraph.html')

t = np.linspace(1, 101, 1000)
A, n = 1, -0.65
y1 = np.zeros(1000)
y2 = A*np.power(t, n)
source = ColumnDataSource(dict(t=t, y1=y1, y2=y2))
source_sel = ColumnDataSource(dict(t=t, y1=y1, y2=y2))

start = TextInput(value="0", title="Start", width = 200)
end = TextInput(value="100", title="End", width = 200)

plot = figure(title=None, plot_width=900, plot_height=300)
#Varea = plot.varea(x="t", y1="y1", y2="y2", fill_color="#f46d43", source = source)
plot.line(x="t", y="y2", line_color="#f46d43", source = source)

plot.varea(x="t", y1="y1", y2="y2", fill_color="black", source = source_sel)

callback = CustomJS(args=dict(source=source,
source_sel = source_sel,
start = start,
end = end), code="""
var data = source.data
var d1 = source_sel.data
d1['t'] = [];
d1['y1'] = [];
d1['y2'] = [];

// Can be made faster if `data.t` is sorted and you use binary search.
const find_closest_idx = (val) => {
return data.t.reduce(({delta, idx}, curr, curr_idx) => {
const curr_delta = Math.abs(curr - val);
return (delta === undefined || curr_delta < delta) ? {delta: curr_delta, idx: curr_idx} : {delta, idx};
}, {}).idx;
}

const start_idx = find_closest_idx(start.value);
const end_idx = find_closest_idx(end.value);

var area = 0;
var height = 0.1;
var data1 = [];
for (var i = start_idx; i <= (end_idx); i++) {
area += 0.5 * (data.y2[i] + data.y2[i-1]) * height;
d1['t'].push(data.t[i])
d1['y1'].push(data.y1[i])
d1['y2'].push(data.y2[i])

}
console.log(area, d1)

source.change.emit()
source_sel.change.emit()
""")

start.js_on_change('value', callback)
end.js_on_change('value', callback)

show(row(plot, column(start, end)))
``````
1 Like

Just need to figure out how to have it plot the area as text either outside the plot below the Textinput, or maybe as a title.

You can use a plain `Div`, put it right under the text inputs, and set its `text` accordingly. in your `CustomJS` callback.

1 Like

Hei,

Saw your message too late, I used paragrahp:P

``````import numpy as np
from bokeh.models import ColumnDataSource, Grid, LinearAxis, Plot, VArea, TextInput, CustomJS, Band
from bokeh.layouts import column, row
from bokeh.plotting import show, figure
from bokeh.io import output_file
from bokeh.models import Paragraph

output_file('areaundergraph.html')

t = np.linspace(0, 100, 1000)
A, n = 1, -0.65
y1 = np.zeros(1000)
y2 = A*np.power(t, n)
source = ColumnDataSource(dict(t=t, y1=y1, y2=y2))
source_sel = ColumnDataSource(dict(t=[], y1=[], y2=[]))

start = TextInput(value="0", title="Start", width = 200)
end = TextInput(value="100", title="End", width = 200)

plot = figure(title=None, plot_width=900, plot_height=300)
plot.line(x="t", y="y2", line_color="#f46d43", source = source)
#plot.scatter(x="t", y="y2", line_color="#f46d43", source = source)

plot.varea(x="t", y1="y1", y2="y2", fill_color="black", source = source_sel)

par1 = Paragraph(text="""Calculate the area by adding start and end time below:""",
width=200, height=50)
areaval = Paragraph(text="""Area =""",
width=200, height=50)

callback = CustomJS(args=dict(source=source,
source_sel = source_sel,
areaval = areaval,
start = start,
end = end), code="""
var data = source.data
var d1 = source_sel.data
d1['t'] = [];
d1['y1'] = [];
d1['y2'] = [];

// Can be made faster if `data.t` is sorted and you use binary search.
const find_closest_idx = (val) => {
return data.t.reduce(({delta, idx}, curr, curr_idx) => {
const curr_delta = Math.abs(curr - val);
return (delta === undefined || curr_delta < delta) ? {delta: curr_delta, idx: curr_idx} : {delta, idx};
}, {}).idx;
}

const start_idx = find_closest_idx(start.value);
const end_idx = find_closest_idx(end.value);

var area = 0;
var height = 0.1;
var data1 = [];
for (var i = start_idx; i <= (end_idx); i++) {
area += 0.5 * (data.y2[i] + data.y2[i-1]) * height;
d1['t'].push(data.t[i])
d1['y1'].push(data.y1[i])
d1['y2'].push(data.y2[i])

}
areaval.attributes.text = 'Area = ' + String(area)

source.change.emit()
source_sel.change.emit()
areaval.change.emit()

""")

start.js_on_change('value', callback)
end.js_on_change('value', callback)

show(row(plot, column(par1, start, end, areaval)))
``````