Seeking an "isvector" function

These 3 liners solves your issue in an easy way.

function isvector(x::T) where T <: AbstractArray
    isone(length(size(x))) && return true
    (!isone(length(size(x))) && isone(size(x)[1])) && return true
    (!isone(length(size(x))) && isone(size(x)[2])) && return true
end
isvector([1 2 3 4])

true

isvector([1, 2, 3, 4])

true

isvector([1 2 3 4]')

true

isvector([1, 2, 3, 4]')

true

isvector([1 2; 3 4])

false

I believe that the comment was in regard to your function _transform_scalar_to_one_element_vec!. In my previous post I showed why such a function is not necessary, if you let Julia’s type system and dispatch do the work for you.

The problem with some packages is, they do not complain, if the input parameters do not satisfy the necessary format. In case of PlotlyJS there is just no stem-line in your plot, if the input is a scalar instead of a one-element vector, no error-massage, but also no stem-line :frowning:
Therefore, it make sense, to make the situation more robust, and act in case your input to a third party package is scalar when a one-element vector is expected.

Please take a look at the methods in my previous post. They guarantee that if one calls the my_stem_plot function with a scalar for either argument, it gets converted to a one-element vector, before calling the method of my_stem_plot that accepts only vectors.

This is a cleaner, more Julian way to approach the problem you want to solve.

6 Likes

Hi Peter,
thanks for your snippets!
I do not know if you are familiar with plotting by means of PlotlyJS.
I usually use my own functions, to configure my plots.
The notation for a single stem line is as follows:

stem(;   x = [1], y = [2], name = "single stemline")

A whole plot function might look like this, how would you integrate your function into this
plot fuction?

using PlotlyJS

function _plot_single_spectra(num_::Int)
	_amplitudes_Shunt = rand(num_)
	_frequencies_Shunt = collect(range(1, num_))
	_amplitudes_Hall = rand(num_)
	_frequencies_Hall = collect(range(1, num_))

    line_amplitudes_shunt   = scatter(; x =_frequencies_Shunt,  y = _amplitudes_Shunt, name = "Shunt")
    line_amplitudes_hall    = scatter(; x =_frequencies_Hall,   y = _amplitudes_Hall,  name = "Hall", yaxis="y2")
    stem_frequ_Shunt_assumed     = stem(;   x = [0.4*num_], y = [1], name = "f_Shunt(assumed)")
    stem_frequ_Hall_assumed      = stem(;   x = [0.6*num_], y = [1], name = "f_Hall(assumed)", yaxis="y2")
    data = [line_amplitudes_shunt, line_amplitudes_hall, stem_frequ_Shunt_assumed, stem_frequ_Hall_assumed]
    mylayout = Layout(
        title_text       = "FFT spectrum",
        xaxis_title_text = "Frequency / Hz",
        yaxis_title_text = "Amplitude Shunt",
		# yaxis_type       = "log",
        yaxis2 = PlotlyJS.attr(
            title      = "Amplitude Hall",
			# type       = "log",
            overlaying = "y",
            side       = "right"
            )
        )
    # ---
    return Plot(data, mylayout)
end

# ---
fn_plot = "c:/tmp/plt/test.pdf"
num_ = round(Int, 0.01 * 24999876)

# ---
hdl_plt = _plot_single_spectra(num_)

I was not familiar with PlotlyJS and I agree the situation is a bit messier than I had assumed. But if you’re worried about accidentally using scalars for x and y then I guess I would use the following definitions:

mystem(x::AbstractVector, y::AbstractVector; kwargs...) = stem(; x, y, kwargs...)
mystem(x::Number, y::Number; kwargs...) = mystem([x], [y]; kwargs...)

and then in your function _plot_single_spectra I would replace the two calls to stem with the folllowing calls:

stem_frequ_Shunt_assumed = mystem(0.4*num_, 1; name = "f_Shunt(assumed)")
stem_frequ_Hall_assumed  = mystem(0.6*num_, 1; name = "f_Hall(assumed)", yaxis="y2")

or you could retain the square brackets around the x and y values, if desired.

edit: Note that i corrected a couple of typos

Can’t you just

function plotstem(x, y)
    plot(stem(; x=isempty(size(x)) ? [x] : x, y=isempty(size(y)) ? [y] : y))
end

?

Here is an alternative that retains exactly the same syntax as your existing stem calls:

mystem2(; x, y, kwargs...) = stem(; x = n2v(x), y = n2v(y), kwargs...)
n2v(x::Number) = [x]
n2v(x::AbstractVector) =  length(x) == 1 ? x : throw(ArgumentError("length must be 1"))

With these, you can just substitute mystem2 for stem in your function _plot_single_spectra. It will work equally well if you call mystem2 with x and/or y being either a number or a 1-element vector. Otherwise you will get an error.

Stefan, after studying PlotlyJS a bit and iterating a bit more, I think I have a solution you will like. The only change necessary in your code is to replace using PlotlyJS by the following:


function stem(; x=nothing, y, kwargs...)
    if isnothing(x)
        return PlotlyBase.stem(; y = n2v(y), kwargs...)
    else
        return PlotlyBase.stem(; x = n2v(x), y = n2v(y), kwargs...)
    end
end
n2v(x::Number) = [x]
n2v(x::AbstractVector) = isone(length(x)) ? x : throw(ArgumentError("length must be 1"))

using PlotlyJS

It is important that the definition of stem above precede using PlotlyJS. Now you can call stem exactly as before, except that the x and y keyword arguments can be either numbers or single-element vectors. For example, suppose you forget to surround the y argument with braces:

julia> num_ = round(Int, 0.01 * 24999876)
249999

julia> stem_frequ_Hall_assumed = stem(;   x = [0.6*num_], y = 1, name = "f_Hall(assumed)", yaxis="y2")
scatter with fields error_y, hoverinfo, marker, mode, name, text, type, x, y, and yaxis

If we examine the fields we see that y is a 1-element vector as desired:

julia> stem_frequ_Hall_assumed.fields
Dict{Symbol, Any} with 10 entries:
  :x         => [1.49999e5]
  :mode      => "markers"
  :y         => [1]
  :type      => "scatter"
  :name      => "f_Hall(assumed)"
  :hoverinfo => "text"
  :error_y   => Dict{Symbol, Any}(:color=>"grey", :width=>0, :symmetric=>false, :type=>"data", :visible=>true, :array=>[0], :thickness=>1, :arrayminus=>[1])
  :text      => [1]
  :yaxis     => "y2"
  :marker    => Dict{Any, Any}(:size=>10)

It took me a while to get to this solution–for me that is the nature of developing software. It’s a process of iterative refinement. Usually, the refinements are completed out of public view (like making sausage :wink:).

2 Likes