Creating custom widgets for ruby gem dashing.io - Or combining widgets elements - dashing

I'm working with the dashing ruby gem and I would really like to combine elements of the graph and number widgets. I would like all elements of the graph and include the up/down percentage arrow from the number widget.
I've never done much work with ruby and I understand there are a few files within the widget that might need to be changed.
I've setup a few nice widgets and I'm using a job to pull data from a redis db to populate. I've added the following to the graph.html page:
<p class="change-rate">
<i data-bind-class="arrow"></i><span data-bind="difference"></span>
</p>
This has no effect, and I'm sure that I'm missing something in one of the many files that makes this all work.

Your on the right track, and I've actually put together something very similar but in order to complete what you are trying to do you need to send data to your two new data binds, which would be done with your jobs file and the graph.coffee file.
I'm not sure how exactly you're getting your graph data from redis to your jobs erb file but you will want to setup a couple new variables, for the example I have used nowNumber and lastNumber. These will be the number that the valuation is performed on.
jobs/jobname.erb
send_event('graph', points: points, current: nowNumber, last: lastNumber )
If you print this out you will get something like this:
{:points=>[{:x=>6, :y=>64}, {:x=>5, :y=>62}, {:x=>4, :y=>56}], :current=>57, :last=>64}
Tweak your graph/graph.coffee file:
# The following 6 lines aren't needed for your solution, but if you wanted to take advantage of 'warning', 'danger', and 'ok' status classes (changes background color and flashes), just feed your send_event with 'status: [one of the three status names]
if data.status
# clear existing "status-*" classes
$(#get('node')).attr 'class', (i,c) ->
c.replace /\bstatus-\S+/g, ''
# add new class
$(#get('node')).addClass "status-#{data.status}"
#accessor 'difference', ->
if #get('last')
last = parseInt(#get('last'))
current = parseInt(#get('current'))
if last != 0
diff = Math.abs(Math.round((current - last) / last * 100))
"#{diff}%"
else
""
# Picks the direction of the arrow based on whether the current value is higher or lower than the last
#accessor 'arrow', ->
if #get('last')
if parseInt(#get('current')) > parseInt(#get('last')) then 'icon-arrow-up' else 'icon-arrow-down'

Related

Cimplicity Screen - one object/button that is dependent on hundreds of points

So I have created a huge screen that essentially just shows the robot status for every robot in this factory (individually)… At the very end of the project, they decided they want one object on the screen that blinks if any of the 300 robots fault. I am trying to think of a way to make this work. Maybe a global script of some kind? Problem is, I do not do much scripting in Cimplicity, so any help is appreciated.
All the points that are currently used on this screen (to indicate a fault) have very similar names… as in, the beginning is the same… so I was thinking of a script that could maybe recognize if a bit is high based on PART of it's string name characteristic. The end will change a little each time, but I am sure there is a way to only look for part of a string and negate the rest. If the end has to be hard coded, that's fine.
You can use a Python script in Cimplicity.
I will not go into detail on the use of python in Cimplicity, which is well described in the documentation indicated above.
Here's an example of what can be done... note that I don't have a way to test it and, of course, this will work if the name of your robots in the declaration follows the format Robot_1, Robot_2, Robot_3 ... Robot_10 ... Robot_300 and it also depends on the Name and the Type of the fault variable... as you didn't define it, I imagine it can be an integer, with ZERO indicating no error. But if you use something other than that, you can easily change it.
import cimplicity
(...)
OneRobotWithFault = False
# Here you get the values and check for fault
for i in range(0, 300):
pointName = f'MyFactory.Robot_{i}.FaultCode'
robotFaultCode = cimplicity.point_get(pointName)
if robotFaultCode > 0:
OneRobotWithFault = True
break
# Set the status to the variable "WeHaveRobotWithFault"
cimplicity.point_set("WeHaveRobotWithFault", OneRobotWithFault)

ipywidgets, problem using a Text widget to filter options in a Select widget with automatic updates/ real-time interaction

I'm trying to use ipywidgets for the first time to make an interactive browser in Jupyter Notebooks of Census data using the census module. In general I'm having difficulty learning the widget library. My plan is to use the Text widget to enter a search term that will then filter the available census 'concepts' or table options (example: "Means of transportation to work") in a Selection or SelectMultiple widget. The selections made will then filter and show available data variables / columns for that census 'concept' or table (example: "car", "truck", "bus", "bike", "walk")
For reference, this first code block is what I got to work in a normal Jupyter Notebook with no widgets (at the bottom is the ipywidget attempt I'm having trouble with). I'm just starting to explore widgets and don't know anyone with experience with them. Any assistance is greatly appreciated!
*Note, running my code would require a census api key from census.gov/data/developers.html, the "Request a KEY" option on the left side menu, and change the ckey="" variable in the code below to your key. Keys are free, as it is publicly available government data.
#This section works for me as standalone code, not using ipywidgets
#Show all concepts to get an idea of search phrases to use
concepts_all = list([n for n in sorted(list([f"{t['description']}" for t in c.acs5.tables()]))]);
#This is a VERY long list, so I commented out printing it
#[print(c) for c in concepts_all]
#Apply a search phrase and limit options accordingly
concept_description_search_phrase = "Means of transportation to work by travel"
[print(f"{table['name']}: {table['description']}") for table in c.acs5.tables() if concept_description_search_phrase.lower() in table['description'].lower()]
#From the filtered options, chose one specific one to search, and display available data for it
concept_full_description = 'MEANS OF TRANSPORTATION TO WORK BY TRAVEL TIME TO WORK'
variable_filter_phrase = 'bicycle'
df = [pd.read_json(table['variables']) for table in c.acs5.tables() if table['description'] == concept_full_description][0]
acs_variable_names_list = [row.name for n,row in df.iterrows()
if variable_filter_phrase.lower() in row['variables']['label'].lower()
and 'Annotation' not in row['variables']['label']
and 'Margin of Error' not in row['variables']['label']]
acs_variable_names_list
# This is the final list I want out from a ipywidgets based workflow
# Which are variable columns, (ex: number of people in each census block group that
# have a 10, 15, 30, 45, 60+ minute commute by bicycle
########################################################
I'm having trouble getting the ipywidgets to work properly, where the Text box will interactively filter the available options in the Selection menu. I can only get it to work on a default value for the Text widget, but not off of the interactive menu. I'm basically going for the effect of the Combobox widget, but with the option to select more than one item if desired, and still see all the available options after a selection is made (so I'm thinking to use a Text and Select widget together instead of Combobox). This is my attempt so far:
out = widgets.Output()
display(out)
search_phrase = widgets.Text(
value = 'transportation',
layout = widgets.Layout(width = '900px')
)
select_multiple = widgets.SelectMultiple(
options = [n for n in concepts_all if search_phrase.value.lower() in n.lower()],
layout = widgets.Layout(width='900px',height='250px'),
ensure_option = True,
disabled=False
)
def search_change(change):
output.clear_output()
display(search_phrase)
with output:
if change.new == '':
print('new search')
return search_phrase.value
else:
print('same search')
return search_phrase.value
display(search_phrase)
display(select_multiple)
#Issue here with getting the search_phrase to properly filter options in select_multiple
#
concept_full_description = select_multiple.value[0] #Using Widgets
variable_filter_phrase = widgets.Text(value = 'bicy')
display(variable_filter_phrase) #Issue, this text box won't automatically filter the output
#it is only filtering by the default value
df = [pd.read_json(table['variables']) for table in c.acs5.tables() if table['description'] == concept_full_description][0]
acs_variable_names_list = [row.name for n,row in df.iterrows()
if variable_filter_phrase.value.lower() in row['variables']['label'].lower()
and 'Annotation' not in row['variables']['label']
and 'Margin of Error' not in row['variables']['label']] #print(row.name,": ",row['variables']['label'])
acs_variable_names_list
#This is the desired output list that will go into later workflows
I think I'm doing something wrong either in use of the .observe(), .interact(), or output /clear_output process. I'm just learning the ipywidgets and am even having trouble getting this to work in a basic example trying to follow the ipywidgets.readthedocs.io documentation.

How to read info on voltage/beam energy, imaging mode, acquisition date/timestamp, etc. from image meta-data? (Tags)

DM scripting beginner here, almost no programming skills.
I would like to know the commands to access all the metadata of DM images/spectra.
I realized that all my STEM images at 80 kV taken between 2 dates (let's say 02.11.2017-05.04.2019) have the scale calibration wrong by the same factor (scale of all such images needs to be multiplied by 1.21).
I would like to write a script which multiplies the scale value by a factor only for images in scanning mode at 80 kV taken during a period for all images in a folder with subfolders or for all images opened in DM and save the new scale value.
I checked this website http://digitalmicrograph-scripting.tavernmaker.de/other%20resources/Old-DMHelp/AllFunctions.html but only found how to call the scale value (ImageGetDimensionCalibration). I have a general idea how to write the script based on other scripts if I find out how to call the metadata.
If anyone can write the whole script for me I would greatly appreciate your effort.
All general meta-data is organized in the image tag-structure
You can see this, if you open the Image Display Info of an image. (Via the menu, or by pressing CTRL + D) and then browse to the "Tags" section:
All info on the right are image tags and they are organized in a hierarchical tree.
How this tree looks like, and what information is written where, is totally open and will depend on what GMS version you are using, how the hardware is configured etc. Also custom scripts might alter this information.
So for a scripting start, open the data you want to modify and have a look in this tree.
Hint: The following min-script can be useful. It opens a tag-browsing window for the front-most image but as a modeless dialog (i.e. you can keep it open and interact with other parts):
GetFrontImage().ImageGetTagGroup().TagGroupOpenBrowserWindow(0)
The information you need to check against is most probably found in the Microscope Info sub-tree. Here, usually all information gathered from the microscope during acquisition is stored. What is there, will depend on your system and how it is set up.
The information of the STEM image acquisition - as far as the scanning engine and detector is concerned - is most probably in the DigiScan sub-tree.
The Data Bar sub-tree usually contains date and time of creation etc.
Calibration values are not stored in the image tag-structure
What you will not find in this tag-structure is the image calibration, i.e. the values actually used by DM to display calibrated values. These values are "one level up" so to speak here:
This is important to know in the following for your script, because you will need different commands for both the "meta-data" from the tags, and the "calibration" you want to change.
Accessing meta-data by script
The script-commands you need to read from the tags are all described in the F1 help documentation here:
Essentially, you need a command to get the "root" TagGroup of an image, which is ImageGetTagGroup() and then you traverse within this tree.
This might seem confusing - because there are a lot of slightly different commands for the different types of stored tags - but the essential bits are easy:
All "Paths" through the tree are just the individual names (typed exactly)
For each "branch" you have to use a single colon :
The commands to set/get a tag-value all require as input the "root" tagGroup object and the "path" as a string. The get commands require a variable of matching type to store the value in, the set commands need the value which should be written.
= The get commands themeselves return true or false depending on whether or not a tag-path could be found and the value could be read.
So the following script would read the "Imaging Mode" from the tags of the image shown as example above:
string mode
GetFrontImage().ImageGetTagGroup().TagGroupGetTagAsString( "Microscope Info:Imaging Mode", mode )
OKDialog( "Mode: " + mode )
and in a little more verbose form:
string mode // variable to hold the value
image img // variable for the image
string path // variable/constant to specify the where
TagGroup tg // variable to hold the "tagGroup" object
img := GetFrontImage() // Use the selected image
tg = img.ImageGetTagGroup() // From the image get the tags (root)
path = "Microscope Info:Imaging Mode" // specify the path
if ( tg.TagGroupGetTagAsString( path, mode ) )
OKDialog( "Mode: " + mode )
else
Throw( "Tag not found" )
If the tag is not a string but a value, you will need the according commands, i.e.
TagGroupGetTagAsNumber().

How to add a custom column to OTRS ticket view (dashboard)

I've been trying to append some more data to an OTRS ticket (newest version). And I have managed to get the information in the ticket data alright, however, I do not know how to access it in the view. Most data seems to be parsed through via $QData/$Data, however, all I get when I print my variable is 16/12.
%CustomerCompanyName = $Self->{CustomerCompanyObject}->CustomerCompanyGet( CustomerID => $Ticket{CustomerID} );
#}
$Ticket{CustomerCompanyName} = \%CustomerCompanyName;
And I want to access it in the dtl AgentDashboardTicketGeneric.dtl, however, $Data{"CustomerCompanyName"} is empty. I've managed to get the hash or 16/12 out. Again, in the variable $Ticket we've managed to dump the variable and see the data is actually in there (without actually being able to access it, we can't figure out which data type it is and have tried all possible ways we could think of).
Edit: I figured it out. It worked with Johannes solution, however, the value of the column in the SysConfig had to be 2, and not 1.
you can access all Ticket Data through the User Interface. In every widget, in the top right corner you can access the settings and remove, add, sort columns.
If you need the Customer Company data, which are not bound to the ticket-data, then you need to modify / extend the given module (Kernel::Output::HTML::DashboardTicketGeneric). Thats the reason why $Data{"CustomerCompanyName"} is empty, because the customer company stuff is not loaded there.
IMHO you don't need to modify the dtl. Add the new column in the sysconfig:
HTTP://OTRSHOST/otrs/index.pl?Action=AdminSysConfig;Subaction=Edit;SysConfigSubGroup=Frontend%3A%3AAgent%3A%3ADashboard;SysConfigGroup=Ticket
Add the new Column "CompanyName" to every widgets DefaultColumns.
(Hint: here you can also add DynamicFields using DynamicField_XXX)
Then modify the Code in DashboardTicketGeneric.pm
First: add the module (around L:21)
use Kernel::System::CustomerCompany;
after that initiate the module (after CustomerUserObject around L: 44)
$Self->{CustomerCompanyObject} = Kernel::System::CustomerCompany->new(%Param);
Then add the logic to the module (around L: 1414 - after the customer name block:
elsif ( $Column eq 'CompanyName' ) {
# get customer company name
my %CompanyData;
if ( $Ticket{CustomerID} ) {
%CompanyData = $Self->{CustomerCompanyObject}->CustomerCompanyGet(
CustomerID => $Ticket{CustomerID},
);
}
$DataValue = $CompanyData{CustomerCompanyName};
}
Then delete the cache (..bin/otrs.DeleteCache.pl), because widgets use caching and your changes won't apply fast enough ;)
Add the column to your widget (top right corner in the widget -> Settings) and you should be fine.
Update: place the "new Module" in the custom folder
Custom/Kernel/Output/HTML/DashboardTicketGeneric.pm
regards
Johannes

Traversing DOM nodes in CKEditor-4

We have a bug at this CKEditor plugin, and a generic solution is like to this, applying a generic string-filter only to terminal text nodes of the DOM.
QUESTION: how (need code example) to traverse a selection node (editor.getSelection()) by CKEditor-4 tools, like CKEDITOR.dom.range?
First step will be to get ranges from current selection. To do this just call:
var ranges = editor.getSelection().getRanges();
This gives you an array of ranges, because theoretically (and this theory is proved only by Firefox) one selection can contain many ranges. But in 99% of real world cases you can just handle the first one and ignore other. So you've got range.
Now, the easiest way to iterate over each node in this range is to use CKEDITOR.dom.walker. Use it for example this way:
var walker = new CKEDITOR.dom.walker( range ),
node;
while ( ( node = walker.next() ) ) {
// Log values of every text node in range.
console.log( node.type == CKEDITOR.NODE_TEXT && node.getText() );
}
However, there's a problem with text nodes at the range's boundaries. Consider following range:
<p>[foo<i>bar</i>bo]m</p>
This will log: foo, bar, and bom. Yes - entire bom, because it is a single node and walker does not modify DOM (there's a bug in documentation).
Therefore you should check every node you want to transform if it equals range's startContainer or endContainer and if yes - transform only that part of it which is inside range.
Note: I don't know walker's internals and I'm not sure whether you can modify DOM while iterating over it. I'd recommend first caching nodes and then making changes.