dynamic customjs_transform

Hello

is it possible to have the CustomJSTransform applied every time the plot is rendered ?
(to extend github example bokeh/examples/plotting/file/customjs_transform.py in order to dynamically render relative values)

At the moment i use a custom extension for Line that transforms sx,sy dynamically, but the problem is that the lines disappear during the zoom, which is not nice visually.

Also, in the custom line extension, using simply invert/compute does not work, have to recalculate sx,sy from scratch.

It should be simpler than that, but i don’t know how !?

The complete example below.

import numpy as np

from bokeh.document import Document

from bokeh.embed import file_html

from bokeh.models import ColumnDataSource, CustomJSTransform

from bokeh.plotting import figure

from bokeh.resources import INLINE

from bokeh.sampledata.stocks import AAPL, GOOG

from bokeh.util.browser import view

JS_CODE_MYLINE = “”"

import {RBush} from “core/util/spatial”

import {Line, LineView} from “models/glyphs/line”

import * as p from “core/properties”

export class MyLineView extends LineView

clip: (x,x0,x1) →

if (x < x0)

    x = x0

else if (x > x1)

    x = x1

return x

_render: (ctx, indices, {sx, sy}) →

normalize = @model.properties.normalize.value()

if !normalize

    super(ctx, indices, {sx, sy})

    return null

r = @renderer

sy0 = r.yscale.target_range.end

sy1 = r.yscale.target_range.start

h = ctx.canvas.height

offset = sy1 - (h - sy0)

y0 = r.yscale.source_range.start

y1 = r.yscale.source_range.end

istart = @clip(Math.ceil(r.xscale.source_range.start),0,sx.length-1)

iend = @clip(Math.floor(r.xscale.source_range.end),0,sx.length-1)

ynorm = 1

for i in [istart...iend+1]

    v = @_y[i]

    if v != 0

        ynorm = v

        break

for i in indices

    sy[i] = ( (sy1-sy0) / (y1-y0) )*(@_y[i]/ynorm-y0) + sy0 - offset

super(ctx, indices, {sx, sy})

return null

export class MyLine extends Line

default_view: MyLineView

type: ‘MyLine’

@define {

normalize: [ p.Bool, false ]

}

“”"

from bokeh.core.properties import Float,Bool

from bokeh.models import Line

from bokeh.util.compiler import CoffeeScript

class MyLine(Line):

__implementation__ = CoffeeScript(JS_CODE_MYLINE)

normalize = Bool(False)

y1, y2 = AAPL[‘adj_close’], GOOG[‘adj_close’]

n = min(len(y1),len(y2))

cds1 = ColumnDataSource(data=dict(x=np.arange(n),y=y1[-n:] ))

cds2 = ColumnDataSource(data=dict(x=np.arange(n),y=y2[-n:] ))

plot = figure(width=600,height=300,tools=[‘box_zoom’,‘xwheel_zoom’,‘xwheel_pan’])

L1 = MyLine(x=“x”, y=“y”, normalize=True,line_color=‘red’); plot.add_glyph(cds1, L1)

L2 = MyLine(x=“x”, y=“y”, normalize=True); plot.add_glyph(cds2, L2)

doc = Document()

doc.add_root(plot)

if name == “main”:

doc.validate()

filename = "ynorm_line.html"

with open(filename, "w") as f:

    f.write(file_html(doc, INLINE, "line custom model with ynorm"))

print("Wrote %s" % filename)

view(filename)

Hi,

A CustomJS transform is applied any time the data->screen mapping happens. So it should happen on any pan/zoom etc. If you need it to happen in other situations where a screen remap would not normally happen, then I think a Custom extension such as you have is probably the way to go.

As for what is going wrong with you code, one issue appears to be that you are using the default DataRange1d models that attempt to auto-range based on the supplied data. This combination is not really consistent, since you are basically trying to do something similar on your own in the glyph _render call. However, I'm not really sure what you are actually trying to accomplish, so I can't spend more time digging into it. However, I would say that trying to transform things in _render is probably not the best strategy in general. The _render method assumes everything is already mapped, so doing your own updates there is likely to confuse other codepaths that rely on that assumption (e.g. data ranges and others). You might want to consider overriding the _map_data method instead.

Thanks,

Bryan

···

On Dec 18, 2017, at 06:31, chupach <[email protected]> wrote:

Hello

is it possible to have the CustomJSTransform applied every time the plot is rendered ?
(to extend github example bokeh/examples/plotting/file/customjs_transform.py in order to dynamically render relative values)

At the moment i use a custom extension for Line that transforms sx,sy dynamically, but the problem is that the lines disappear during the zoom, which is not nice visually.

Also, in the custom line extension, using simply invert/compute does not work, have to recalculate sx,sy from scratch.
It should be simpler than that, but i don't know how !?

The complete example below.

import numpy as np

from bokeh.document import Document

from bokeh.embed import file_html

from bokeh.models import ColumnDataSource, CustomJSTransform

from bokeh.plotting import figure

from bokeh.resources import INLINE

from bokeh.sampledata.stocks import AAPL, GOOG

from bokeh.util.browser import view

JS_CODE_MYLINE = """

import {RBush} from "core/util/spatial"

import {Line, LineView} from "models/glyphs/line"

import * as p from "core/properties"

export class MyLineView extends LineView

  clip: (x,x0,x1) ->

    if (x < x0)

        x = x0

    else if (x > x1)

        x = x1

    return x

  _render: (ctx, indices, {sx, sy}) ->

    normalize = @model.properties.normalize.value()

    if !normalize

        super(ctx, indices, {sx, sy})

        return null

    r = @renderer

    sy0 = r.yscale.target_range.end

    sy1 = r.yscale.target_range.start

    h = ctx.canvas.height

    offset = sy1 - (h - sy0)

    y0 = r.yscale.source_range.start

    y1 = r.yscale.source_range.end

    istart = @clip(Math.ceil(r.xscale.source_range.start),0,sx.length-1)

    iend = @clip(Math.floor(r.xscale.source_range.end),0,sx.length-1)

    ynorm = 1

    for i in [istart...iend+1]

        v = @_y[i]

        if v != 0

            ynorm = v

            break

    for i in indices

        sy[i] = ( (sy1-sy0) / (y1-y0) )*(@_y[i]/ynorm-y0) + sy0 - offset

    super(ctx, indices, {sx, sy})

    return null

export class MyLine extends Line

  default_view: MyLineView

  type: 'MyLine'

  @define {

    normalize: [ p.Bool, false ]

  }

"""

from bokeh.core.properties import Float,Bool

from bokeh.models import Line

from bokeh.util.compiler import CoffeeScript

class MyLine(Line):

    __implementation__ = CoffeeScript(JS_CODE_MYLINE)

    normalize = Bool(False)

y1, y2 = AAPL['adj_close'], GOOG['adj_close']

n = min(len(y1),len(y2))

cds1 = ColumnDataSource(data=dict(x=np.arange(n),y=y1[-n:] ))

cds2 = ColumnDataSource(data=dict(x=np.arange(n),y=y2[-n:] ))

plot = figure(width=600,height=300,tools=['box_zoom','xwheel_zoom','xwheel_pan'])

L1 = MyLine(x="x", y="y", normalize=True,line_color='red'); plot.add_glyph(cds1, L1)

L2 = MyLine(x="x", y="y", normalize=True); plot.add_glyph(cds2, L2)

doc = Document()

doc.add_root(plot)

if __name__ == "__main__":

    doc.validate()

    filename = "ynorm_line.html"

    with open(filename, "w") as f:

        f.write(file_html(doc, INLINE, "line custom model with ynorm"))

    print("Wrote %s" % filename)

    view(filename)

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/aae39068-82f6-4b69-8e6e-20079ac39c79%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Thanks Bryan, works now beautifully ! (and much simpler as well)

Show now lines in relative units without flickering

updated example:

import numpy as np

from bokeh.document import Document

from bokeh.embed import file_html

from bokeh.models import ColumnDataSource

from bokeh.plotting import figure

from bokeh.resources import INLINE

from bokeh.sampledata.stocks import AAPL, GOOG

from bokeh.util.browser import view

JS_CODE_MYLINE = “”"

import {RBush} from “core/util/spatial”

import {Line, LineView} from “models/glyphs/line”

import * as p from “core/properties”

export class MyLineView extends LineView

clip: (x,x0,x1) →

if (x < x0)

x = x0

else if (x > x1)

x = x1

return x

_map_data: () →

r = @renderer

y0 = r.yscale.source_range.start

y1 = r.yscale.source_range.end

istart = @clip(Math.ceil(r.xscale.source_range.start),0,@sx.length-1)

ynorm = @_y[istart]

y =

for i in [0…@_y.length]

y.push(@_y[i]/ynorm)

[@sx, @sy] = @map_to_screen(@_x, y)

export class MyLine extends Line

default_view: MyLineView

type: ‘MyLine’

@define {

normalize: [ p.Bool, false ]

}

“”"

from bokeh.core.properties import Float,Bool

from bokeh.models import Line

from bokeh.util.compiler import CoffeeScript

class MyLine(Line):

implementation = CoffeeScript(JS_CODE_MYLINE)

normalize = Bool(False)

y1, y2 = AAPL[‘adj_close’], GOOG[‘adj_close’]

n = min(len(y1),len(y2))

cds1 = ColumnDataSource(data=dict(x=np.arange(n),y=y1[-n:] ))

cds2 = ColumnDataSource(data=dict(x=np.arange(n),y=y2[-n:] ))

plot = figure(width=600,height=300,tools=[‘box_zoom’,‘wheel_zoom’,‘xwheel_pan’])

L1 = MyLine(x=“x”, y=“y”, normalize=True,line_color=‘red’); plot.add_glyph(cds1, L1)

L2 = MyLine(x=“x”, y=“y”, normalize=True); plot.add_glyph(cds2, L2)

plot.y_range.start=0

plot.y_range.end=20

doc = Document()

doc.add_root(plot)

if name == “main”:

doc.validate()

filename = “ynorm_line.html”

with open(filename, “w”) as f:

f.write(file_html(doc, INLINE, “line custom model with ynorm”))

print(“Wrote %s” % filename)

view(filename)

···

On Monday, January 1, 2018 at 10:20:20 PM UTC+1, Bryan Van de ven wrote:

Hi,

A CustomJS transform is applied any time the data->screen mapping happens. So it should happen on any pan/zoom etc. If you need it to happen in other situations where a screen remap would not normally happen, then I think a Custom extension such as you have is probably the way to go.

As for what is going wrong with you code, one issue appears to be that you are using the default DataRange1d models that attempt to auto-range based on the supplied data. This combination is not really consistent, since you are basically trying to do something similar on your own in the glyph _render call. However, I’m not really sure what you are actually trying to accomplish, so I can’t spend more time digging into it. However, I would say that trying to transform things in _render is probably not the best strategy in general. The _render method assumes everything is already mapped, so doing your own updates there is likely to confuse other codepaths that rely on that assumption (e.g. data ranges and others). You might want to consider overriding the _map_data method instead.

Thanks,

Bryan

On Dec 18, 2017, at 06:31, chupach [email protected] wrote:

Hello

is it possible to have the CustomJSTransform applied every time the plot is rendered ?

(to extend github example bokeh/examples/plotting/file/customjs_transform.py in order to dynamically render relative values)

At the moment i use a custom extension for Line that transforms sx,sy dynamically, but the problem is that the lines disappear during the zoom, which is not nice visually.

Also, in the custom line extension, using simply invert/compute does not work, have to recalculate sx,sy from scratch.

It should be simpler than that, but i don’t know how !?

The complete example below.

import numpy as np

from bokeh.document import Document

from bokeh.embed import file_html

from bokeh.models import ColumnDataSource, CustomJSTransform

from bokeh.plotting import figure

from bokeh.resources import INLINE

from bokeh.sampledata.stocks import AAPL, GOOG

from bokeh.util.browser import view

JS_CODE_MYLINE = “”"

import {RBush} from “core/util/spatial”

import {Line, LineView} from “models/glyphs/line”

import * as p from “core/properties”

export class MyLineView extends LineView

clip: (x,x0,x1) →

if (x < x0)
    x = x0
else if (x > x1)
    x = x1
return x

_render: (ctx, indices, {sx, sy}) →

normalize = @model.properties.normalize.value()
if !normalize
    super(ctx, indices, {sx, sy})
    return null
r = @renderer
sy0 = r.yscale.target_range.end
sy1 = r.yscale.target_range.start
h = ctx.canvas.height
offset = sy1 - (h - sy0)
y0 = r.yscale.source_range.start
y1 = r.yscale.source_range.end
istart = @clip(Math.ceil(r.xscale.source_range.start),0,sx.length-1)
iend = @clip(Math.floor(r.xscale.source_range.end),0,sx.length-1)
ynorm = 1
for i in [istart...iend+1]
    v = @_y[i]
    if v != 0
        ynorm = v
        break
for i in indices
    sy[i] = ( (sy1-sy0) / (y1-y0) )*(@_y[i]/ynorm-y0) + sy0 - offset
super(ctx, indices, {sx, sy})
return null

export class MyLine extends Line

default_view: MyLineView

type: ‘MyLine’

@define {

normalize: [ p.Bool, false ]

}

“”"

from bokeh.core.properties import Float,Bool

from bokeh.models import Line

from bokeh.util.compiler import CoffeeScript

class MyLine(Line):

__implementation__ = CoffeeScript(JS_CODE_MYLINE)
normalize = Bool(False)

y1, y2 = AAPL[‘adj_close’], GOOG[‘adj_close’]

n = min(len(y1),len(y2))

cds1 = ColumnDataSource(data=dict(x=np.arange(n),y=y1[-n:] ))

cds2 = ColumnDataSource(data=dict(x=np.arange(n),y=y2[-n:] ))

plot = figure(width=600,height=300,tools=[‘box_zoom’,‘xwheel_zoom’,‘xwheel_pan’])

L1 = MyLine(x=“x”, y=“y”, normalize=True,line_color=‘red’); plot.add_glyph(cds1, L1)

L2 = MyLine(x=“x”, y=“y”, normalize=True); plot.add_glyph(cds2, L2)

doc = Document()

doc.add_root(plot)

if name == “main”:

doc.validate()
filename = "ynorm_line.html"
with open(filename, "w") as f:
    f.write(file_html(doc, INLINE, "line custom model with ynorm"))
print("Wrote %s" % filename)
view(filename)


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/aae39068-82f6-4b69-8e6e-20079ac39c79%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

I am glad it's working!

Bryan

···

On Jan 2, 2018, at 03:22, chupach <[email protected]> wrote:

Thanks Bryan, works now beautifully ! (and much simpler as well)

Show now lines in relative units without flickering

updated example:

import numpy as np
from bokeh.document import Document
from bokeh.embed import file_html
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.resources import INLINE
from bokeh.sampledata.stocks import AAPL, GOOG
from bokeh.util.browser import view

JS_CODE_MYLINE = """
import {RBush} from "core/util/spatial"
import {Line, LineView} from "models/glyphs/line"
import * as p from "core/properties"

export class MyLineView extends LineView
  clip: (x,x0,x1) ->
    if (x < x0)
        x = x0
    else if (x > x1)
        x = x1
    return x

  _map_data: () ->
    r = @renderer

    y0 = r.yscale.source_range.start
    y1 = r.yscale.source_range.end
    istart = @clip(Math.ceil(r.xscale.source_range.start),0,@sx.length-1)

    ynorm = @_y[istart]
    y =
    for i in [0...@_y.length]
       y.push(@_y[i]/ynorm)
    [@sx, @sy] = @map_to_screen(@_x, y)

export class MyLine extends Line
  default_view: MyLineView
  type: 'MyLine'
  @define {
    normalize: [ p.Bool, false ]
  }
"""

from bokeh.core.properties import Float,Bool
from bokeh.models import Line
from bokeh.util.compiler import CoffeeScript

class MyLine(Line):
    __implementation__ = CoffeeScript(JS_CODE_MYLINE)
    normalize = Bool(False)

y1, y2 = AAPL['adj_close'], GOOG['adj_close']
n = min(len(y1),len(y2))
cds1 = ColumnDataSource(data=dict(x=np.arange(n),y=y1[-n:] ))
cds2 = ColumnDataSource(data=dict(x=np.arange(n),y=y2[-n:] ))

plot = figure(width=600,height=300,tools=['box_zoom','wheel_zoom','xwheel_pan'])

L1 = MyLine(x="x", y="y", normalize=True,line_color='red'); plot.add_glyph(cds1, L1)
L2 = MyLine(x="x", y="y", normalize=True); plot.add_glyph(cds2, L2)

plot.y_range.start=0
plot.y_range.end=20

doc = Document()
doc.add_root(plot)

if __name__ == "__main__":
    doc.validate()
    filename = "ynorm_line.html"
    with open(filename, "w") as f:
        f.write(file_html(doc, INLINE, "line custom model with ynorm"))
    print("Wrote %s" % filename)
    view(filename)

On Monday, January 1, 2018 at 10:20:20 PM UTC+1, Bryan Van de ven wrote:
Hi,

A CustomJS transform is applied any time the data->screen mapping happens. So it should happen on any pan/zoom etc. If you need it to happen in other situations where a screen remap would not normally happen, then I think a Custom extension such as you have is probably the way to go.

As for what is going wrong with you code, one issue appears to be that you are using the default DataRange1d models that attempt to auto-range based on the supplied data. This combination is not really consistent, since you are basically trying to do something similar on your own in the glyph _render call. However, I'm not really sure what you are actually trying to accomplish, so I can't spend more time digging into it. However, I would say that trying to transform things in _render is probably not the best strategy in general. The _render method assumes everything is already mapped, so doing your own updates there is likely to confuse other codepaths that rely on that assumption (e.g. data ranges and others). You might want to consider overriding the _map_data method instead.

Thanks,

Bryan

> On Dec 18, 2017, at 06:31, chupach <[email protected]> wrote:
>
> Hello
>
> is it possible to have the CustomJSTransform applied every time the plot is rendered ?
> (to extend github example bokeh/examples/plotting/file/customjs_transform.py in order to dynamically render relative values)
>
> At the moment i use a custom extension for Line that transforms sx,sy dynamically, but the problem is that the lines disappear during the zoom, which is not nice visually.
>
> Also, in the custom line extension, using simply invert/compute does not work, have to recalculate sx,sy from scratch.
> It should be simpler than that, but i don't know how !?
>
> The complete example below.
>
>
> import numpy as np
>
> from bokeh.document import Document
>
> from bokeh.embed import file_html
>
> from bokeh.models import ColumnDataSource, CustomJSTransform
>
> from bokeh.plotting import figure
>
> from bokeh.resources import INLINE
>
> from bokeh.sampledata.stocks import AAPL, GOOG
>
> from bokeh.util.browser import view
>
>
>
>
>
> JS_CODE_MYLINE = """
>
> import {RBush} from "core/util/spatial"
>
> import {Line, LineView} from "models/glyphs/line"
>
> import * as p from "core/properties"
>
>
>
> export class MyLineView extends LineView
>
> clip: (x,x0,x1) ->
>
> if (x < x0)
>
> x = x0
>
> else if (x > x1)
>
> x = x1
>
> return x
>
>
>
> _render: (ctx, indices, {sx, sy}) ->
>
> normalize = @model.properties.normalize.value()
>
> if !normalize
>
> super(ctx, indices, {sx, sy})
>
> return null
>
>
>
> r = @renderer
>
>
>
> sy0 = r.yscale.target_range.end
>
> sy1 = r.yscale.target_range.start
>
> h = ctx.canvas.height
>
> offset = sy1 - (h - sy0)
>
>
>
> y0 = r.yscale.source_range.start
>
> y1 = r.yscale.source_range.end
>
>
>
> istart = @clip(Math.ceil(r.xscale.source_range.start),0,sx.length-1)
>
> iend = @clip(Math.floor(r.xscale.source_range.end),0,sx.length-1)
>
>
>
> ynorm = 1
>
> for i in [istart...iend+1]
>
> v = @_y[i]
>
> if v != 0
>
> ynorm = v
>
> break
>
>
>
> for i in indices
>
> sy[i] = ( (sy1-sy0) / (y1-y0) )*(@_y[i]/ynorm-y0) + sy0 - offset
>
>
>
> super(ctx, indices, {sx, sy})
>
> return null
>
>
>
> export class MyLine extends Line
>
> default_view: MyLineView
>
> type: 'MyLine'
>
> @define {
>
> normalize: [ p.Bool, false ]
>
> }
>
> """
>
>
>
> from bokeh.core.properties import Float,Bool
>
> from bokeh.models import Line
>
> from bokeh.util.compiler import CoffeeScript
>
>
>
> class MyLine(Line):
>
> __implementation__ = CoffeeScript(JS_CODE_MYLINE)
>
> normalize = Bool(False)
>
>
>
>
>
>
>
> y1, y2 = AAPL['adj_close'], GOOG['adj_close']
>
> n = min(len(y1),len(y2))
>
> cds1 = ColumnDataSource(data=dict(x=np.arange(n),y=y1[-n:] ))
>
> cds2 = ColumnDataSource(data=dict(x=np.arange(n),y=y2[-n:] ))
>
>
>
> plot = figure(width=600,height=300,tools=['box_zoom','xwheel_zoom','xwheel_pan'])
>
>
>
> L1 = MyLine(x="x", y="y", normalize=True,line_color='red'); plot.add_glyph(cds1, L1)
>
> L2 = MyLine(x="x", y="y", normalize=True); plot.add_glyph(cds2, L2)
>
>
>
> doc = Document()
>
> doc.add_root(plot)
>
>
>
> if __name__ == "__main__":
>
> doc.validate()
>
> filename = "ynorm_line.html"
>
> with open(filename, "w") as f:
>
> f.write(file_html(doc, INLINE, "line custom model with ynorm"))
>
> print("Wrote %s" % filename)
>
> view(filename)
>
>
>
>
> --
> You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to bokeh+un...@continuum.io.
> To post to this group, send email to bo...@continuum.io.
> To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/aae39068-82f6-4b69-8e6e-20079ac39c79%40continuum.io\.
> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/c4b6a2c5-f8e5-4c17-9ffa-f1580ea25132%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.