I'm trying to create a simple flot line graph and update it on a timer and I only want to display the last 10 points of data. But I only ever see the axis and not the graph plot. Also, I see the x axis change with the extra data but the y axis remain the same and do not correspond to the additional data. My code is as following:
var dataSet = [];
var PlotData;
var x = 0;
var y = 0;
var plot = null;
function EveryOneSec()
{
if (dataSet.length == 10)
{
dataSet.shift();
}
x++;
y += 2;
dataSet("[" + x + ", " + y + "]");
PlotData = { label: "line 1", data: [ dataSet ], color: "green" };
if (plot == null)
{
plot = $.plot($("#placeholder"), [ PlotData ], { lines: {show: true}, points: {show: true}});
}
else
{
plot.setData([ PlotData ]);
plot.setupGrid();
plot.draw();
}
setTimeout(EveryOneSec, 1000);
}
I have tried with and without the call to setupGrid() but this makes no difference to the axis display or graph plot. The x axis stop changing when I get the ticks 0 to 9 plotted even though x is incrementing past that, and the y axis remains static. I believe the code is correct above in terms of passing arrays of data, so why is the graph not appearing?
OK, you have two problems here.
First, you're not appending to your dataSet correctly. I'm not sure what the syntax you've got is doing, but what you need in each slot of the array is [x,y], which you can achieve with Array.push.
This:
dataSet("[" + x + ", " + y + "]");
Should look like this:
dataSet.push([x , y]);
And when you create your series object PlotData, you don't need to store your data inside of another array, so instead of this:
PlotData = { label: "line 1", data: [ dataSet ], color: "green" };
You need this:
PlotData = { label: "line 1", data: dataSet , color: "green" };
See it working here: http://jsfiddle.net/ryleyb/qJEXH/
Related
I am using Google Vision API, primarily to extract texts. I works fine, but for specific cases where I would need the API to scan the enter line, spits out the text before moving to the next line. However, it appears that the API is using some kind of logic that makes it scan top to bottom on the left side and moving to right side and doing a top to bottom scan. I would have liked if the API read left-to-right, move down and so on.
For example, consider the image:
The API returns the text like this:
“ Name DOB Gender: Lives In John Doe 01-Jan-1970 LA ”
Whereas, I would have expected something like this:
“ Name: John Doe DOB: 01-Jan-1970 Gender: M Lives In: LA ”
I suppose there is a way to define the block size or margin setting (?) to read the image/scan line by line?
Thanks for your help.
Alex
This might be a late answer but adding it for future reference.
You can add feature hints to your JSON request to get the desired results.
{
"requests": [
{
"image": {
"source": {
"imageUri": "https://i.stack.imgur.com/TRTXo.png"
}
},
"features": [
{
"type": "DOCUMENT_TEXT_DETECTION"
}
]
}
]
}
For text which are very far apart the DOCUMENT_TEXT_DETECTION also does not provide proper line segmentation.
The following code does simple line segmentation based on the character polygon coordinates.
https://github.com/sshniro/line-segmentation-algorithm-to-gcp-vision
Here a simple code to read line by line. y-axis for lines and x-axis for each word in the line.
items = []
lines = {}
for text in response.text_annotations[1:]:
top_x_axis = text.bounding_poly.vertices[0].x
top_y_axis = text.bounding_poly.vertices[0].y
bottom_y_axis = text.bounding_poly.vertices[3].y
if top_y_axis not in lines:
lines[top_y_axis] = [(top_y_axis, bottom_y_axis), []]
for s_top_y_axis, s_item in lines.items():
if top_y_axis < s_item[0][1]:
lines[s_top_y_axis][1].append((top_x_axis, text.description))
break
for _, item in lines.items():
if item[1]:
words = sorted(item[1], key=lambda t: t[0])
items.append((item[0], ' '.join([word for _, word in words]), words))
print(items)
You can extract the text based on the bounds per line too, you can use boundyPoly and concatenate the text in the same line
"boundingPoly": {
"vertices": [
{
"x": 87,
"y": 148
},
{
"x": 411,
"y": 148
},
{
"x": 411,
"y": 206
},
{
"x": 87,
"y": 206
}
]
for example this 2 words are in the same "line"
"description": "you",
"boundingPoly": {
"vertices": [
{
"x": 362,
"y": 1406
},
{
"x": 433,
"y": 1406
},
{
"x": 433,
"y": 1448
},
{
"x": 362,
"y": 1448
}
]
}
},
{
"description": "start",
"boundingPoly": {
"vertices": [
{
"x": 446,
"y": 1406
},
{
"x": 540,
"y": 1406
},
{
"x": 540,
"y": 1448
},
{
"x": 446,
"y": 1448
}
]
}
}
I get max and min y and iterate over y to get all potential lines, here is the full code
import io
import sys
from os import listdir
from google.cloud import vision
def read_image(image_file):
client = vision.ImageAnnotatorClient()
with io.open(image_file, "rb") as image_file:
content = image_file.read()
image = vision.Image(content=content)
return client.document_text_detection(
image=image,
image_context={"language_hints": ["bg"]}
)
def extract_paragraphs(image_file):
response = read_image(image_file)
min_y = sys.maxsize
max_y = -1
for t in response.text_annotations:
poly_range = get_poly_y_range(t.bounding_poly)
t_min = min(poly_range)
t_max = max(poly_range)
if t_min < min_y:
min_y = t_min
if t_max > max_y:
max_y = t_max
max_size = max_y - min_y
text_boxes = []
for t in response.text_annotations:
poly_range = get_poly_y_range(t.bounding_poly)
t_x = get_poly_x(t.bounding_poly)
t_min = min(poly_range)
t_max = max(poly_range)
poly_size = t_max - t_min
text_boxes.append({
'min_y': t_min,
'max_y': t_max,
'x': t_x,
'size': poly_size,
'description': t.description
})
paragraphs = []
for i in range(min_y, max_y):
para_line = []
for text_box in text_boxes:
t_min = text_box['min_y']
t_max = text_box['max_y']
x = text_box['x']
size = text_box['size']
# size < max_size excludes the biggest rect
if size < max_size * 0.9 and t_min <= i <= t_max:
para_line.append(
{
'text': text_box['description'],
'x': x
}
)
# here I have to sort them by x so the don't get randomly shuffled
para_line = sorted(para_line, key=lambda x: x['x'])
line = " ".join(map(lambda x: x['text'], para_line))
paragraphs.append(line)
# if line not in paragraphs:
# paragraphs.append(line)
return "\n".join(paragraphs)
def get_poly_y_range(poly):
y_list = []
for v in poly.vertices:
if v.y not in y_list:
y_list.append(v.y)
return y_list
def get_poly_x(poly):
return poly.vertices[0].x
def extract_paragraphs_from_image(picName):
print(picName)
pic_path = rootPics + "/" + picName
text = extract_paragraphs(pic_path)
text_path = outputRoot + "/" + picName + ".txt"
write(text_path, text)
This code is WIP.
In the end, I get the same line multiple times and post-processing to determine the exact values. (paragraphs variable). Let me know if I have to clarify anything
Inspired by Borislav's answer, I just wrote something for python that also works for handwriting. It's messy and I am new to python, but I think you can get an idea of how to do this.
A class to hold some extended data for each word, for example, the average y position of a word, which I used to calculate the differences between words:
import re
from operator import attrgetter
import numpy as np
class ExtendedAnnotation:
def __init__(self, annotation):
self.vertex = annotation.bounding_poly.vertices
self.text = annotation.description
self.avg_y = (self.vertex[0].y + self.vertex[1].y + self.vertex[2].y + self.vertex[3].y) / 4
self.height = ((self.vertex[3].y - self.vertex[1].y) + (self.vertex[2].y - self.vertex[0].y)) / 2
self.start_x = (self.vertex[0].x + self.vertex[3].x) / 2
def __repr__(self):
return '{' + self.text + ', ' + str(self.avg_y) + ', ' + str(self.height) + ', ' + str(self.start_x) + '}'
Create objects with that data:
def get_extended_annotations(response):
extended_annotations = []
for annotation in response.text_annotations:
extended_annotations.append(ExtendedAnnotation(annotation))
# delete last item, as it is the whole text I guess.
del extended_annotations[0]
return extended_annotations
Calculate the threshold.
First, all words a sorted by their y position, defined as being the average of all 4 corners of a word. The x position is not relevant at this moment.
Then, the differences between every word and their following word are calculated. For a perfectly straight line of words, you would expect the differences of the y position between every two words to be 0. Even for handwriting, it should be around 1 ~ 10.
However, whenever there is a line break, the difference between the last word of the former row and the first word of the new row is much greater than that, for example, 50 or 60.
So to decide whether there should be a line break between two words, the standard deviation of the differences is used.
def get_threshold_for_y_difference(annotations):
annotations.sort(key=attrgetter('avg_y'))
differences = []
for i in range(0, len(annotations)):
if i == 0:
continue
differences.append(abs(annotations[i].avg_y - annotations[i - 1].avg_y))
return np.std(differences)
Having calculated the threshold, the list of all words gets grouped into rows accordingly.
def group_annotations(annotations, threshold):
annotations.sort(key=attrgetter('avg_y'))
line_index = 0
text = [[]]
for i in range(0, len(annotations)):
if i == 0:
text[line_index].append(annotations[i])
continue
y_difference = abs(annotations[i].avg_y - annotations[i - 1].avg_y)
if y_difference > threshold:
line_index = line_index + 1
text.append([])
text[line_index].append(annotations[i])
return text
Finally, each row is sorted by their x position to get them into the correct order from left to right.
Then a little regex is used to remove whitespace in front of interpunctuation.
def sort_and_combine_grouped_annotations(annotation_lists):
grouped_list = []
for annotation_group in annotation_lists:
annotation_group.sort(key=attrgetter('start_x'))
texts = (o.text for o in annotation_group)
texts = ' '.join(texts)
texts = re.sub(r'\s([-;:?.!](?:\s|$))', r'\1', texts)
grouped_list.append(texts)
return grouped_list
Based on Borislav Stoilov latest answer I wrote the code for c# for anybody that might need it in the future. Find the code bellow:
public static List<TextParagraph> ExtractParagraphs(IReadOnlyList<EntityAnnotation> textAnnotations)
{
var min_y = int.MaxValue;
var max_y = -1;
foreach (var item in textAnnotations)
{
var poly_range = Get_poly_y_range(item.BoundingPoly);
var t_min = poly_range.Min();
var t_max = poly_range.Max();
if (t_min < min_y) min_y = t_min;
if (t_max > max_y) max_y = t_max;
}
var max_size = max_y - min_y;
var text_boxes = new List<TextBox>();
foreach (var item in textAnnotations)
{
var poly_range = Get_poly_y_range(item.BoundingPoly);
var t_x = Get_poly_x(item.BoundingPoly);
var t_min = poly_range.Min();
var t_max = poly_range.Max();
var poly_size = t_max - t_min;
text_boxes.Add(new TextBox
{
Min_y = t_min,
Max_y = t_max,
X = t_x,
Size = poly_size,
Description = item.Description
});
}
var paragraphs = new List<TextParagraph>();
for (int i = min_y; i < max_y; i++)
{
var para_line = new List<TextLine>();
foreach (var text_box in text_boxes)
{
int t_min = text_box.Min_y;
int t_max = text_box.Max_y;
int x = text_box.X;
int size = text_box.Size;
//# size < max_size excludes the biggest rect
if (size < (max_size * 0.9) && t_min <= i && i <= t_max)
para_line.Add(
new TextLine
{
Text = text_box.Description,
X = x
}
);
}
// here I have to sort them by x so the don't get randomly enter code hereshuffled
para_line = para_line.OrderBy(x => x.X).ToList();
var line = string.Join(" ", para_line.Select(x => x.Text));
var paragraph = new TextParagraph
{
Order = i,
Text = line,
WordCount = para_line.Count,
TextBoxes = para_line
};
paragraphs.Add(paragraph);
}
return paragraphs;
//return string.Join("\n", paragraphs);
}
private static List<int> Get_poly_y_range(BoundingPoly poly)
{
var y_list = new List<int>();
foreach (var v in poly.Vertices)
{
if (!y_list.Contains(v.Y))
{
y_list.Add(v.Y);
}
}
return y_list;
}
private static int Get_poly_x(BoundingPoly poly)
{
return poly.Vertices[0].X;
}
Calling ExtractParagraphs() method will return a list of strings which contains doubles from the file. I also wrote some custom code to treat that problem. If you need any help processing the doubles let me know, and I could provide the rest of the code.
Example:
Text in picture: "I want to make this thing work 24/7!"
Code will return:
"I"
"I want"
"I want to "
"I want to make"
"I want to make this"
"I want to make this thing"
"I want to make this thing work"
"I want to make this thing work 24/7!"
"to make this thing work 24/7!"
"this thing work 24/7!"
"thing work 24/7!"
"work 24/7!"
"24/7!"
I also have an implementation of parsing PDFs to PNGs beacause Google Cloud Vision Api won't accept PDFs that are not stored in the Cloud Bucket. If needed I can provide it.
Happy coding!
I am trying to create a simple line chart with datetime at interval of 5 minutes on the X-axis and some data on the Y-axis,
the data is getting displayed as intended when the chart is displayed, however it shows the date and time both on the X-axis.
I just want to show the dates on the X-axis at one day interval and the both (date & time) inside the chart when user hovers on the data points.
I have put the following code for the "XAxis" attribute inside options section:
xAxis: {
axisLabel: 'Date',
tickFormat: function (d) {
return d3.time.format('%a %b %e %H:%M:%S %Y')(new Date(d))
}
Please suggest on how this can be done.
Usually you can stay with default formatting, it is quite intelligent for almost every case.
But for force formatting use in your case use "%a %d".
To show tooltip you do not have to add format to axis, you have to do smth like:
someD3Elements.on("mouseover", function(d) {
return tooltipElement.style("visibility", "visible")
.text(function() {
return d3.time.format('%a %b %e %H:%M:%S %Y')(new Date(d));
});
})
.on("mousemove", function() {
var event = d3.event;
return tooltipElement.style("top", (event.pageY) + "px").style("left", event.pageX + 10 + "px");
})
.on("mouseout", function() {
return tooltipElement.style("visibility", "hidden");
})
where someD3Elements could be e.g. circles, and tooltipElement could be defined smth like:
tooltip = d3.select("#viewPort")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("");
}
I would like to have both solid and dashed lines for different levels of a factor (grouping variable) in a plot created with nPlot. Any suggestions?
library(reshape2)
library(ggplot2)
library(rCharts)
ecm <- reshape2::melt(economics[,c('date', 'uempmed', 'psavert')], id = 'date')
p7 <- nPlot(value ~ date, group = 'variable', data = ecm, type = 'lineWithFocusChart')
Final result:
uempmed (solid line) and psavert (dashed line)
Another option could be to change the thickness of the lines instead.
Unfortunately nvd3 has stagnated. This is a great example where a pull request that would allow the ability to specify thickness and dashed styling for lines sits unpulled.
Here is the difficult way to potentially solve your problem. We will need to modify the standard rCharts script template to add a callback function for the line styling. See here for a rCharts demo and a live code example.
options(viewer=NULL)
library(reshape2)
library(ggplot2)
library(rCharts)
ecm <- reshape2::melt(economics[,c('date', 'uempmed', 'psavert')], id = 'date')
p7 <- nPlot(value ~ date, group = 'variable', data = ecm, type = 'lineWithFocusChart')
#grab template from
#https://github.com/ramnathv/rCharts/blob/master/inst/libraries/nvd3/layouts/chart.html
#modify to add callback on graph render
p7$setTemplate(script = sprintf("
<script type='text/javascript'>
$(document).ready(function(){
draw{{chartId}}()
});
function draw{{chartId}}(){
var opts = {{{ opts }}},
data = {{{ data }}}
if(!(opts.type==='pieChart' || opts.type==='sparklinePlus' || opts.type==='bulletChart')) {
var data = d3.nest()
.key(function(d){
//return opts.group === undefined ? 'main' : d[opts.group]
//instead of main would think a better default is opts.x
return opts.group === undefined ? opts.y : d[opts.group];
})
.entries(data);
}
if (opts.disabled != undefined){
data.map(function(d, i){
d.disabled = opts.disabled[i]
})
}
nv.addGraph(function() {
var chart = nv.models[opts.type]()
.width(opts.width)
.height(opts.height)
if (opts.type != 'bulletChart'){
chart
.x(function(d) { return d[opts.x] })
.y(function(d) { return d[opts.y] })
}
{{{ chart }}}
{{{ xAxis }}}
{{{ x2Axis }}}
{{{ yAxis }}}
//on legend click, line might be deleted
//when redrawn we need to make dashed again
chart.legend.dispatch.on('legendClick', dashedAfterLegend )
function dashedAfterLegend(){
//to make dashed we need to make sure it is drawn first
//not a js expert so might not be best way to handle
window.setTimeout(function dashedDelay(){
makeDashed(opts)
} , 400)
}
d3.select('#' + opts.id)
.append('svg')
.datum(data)
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
},%s);
};
%s
</script>
"
,
#here is where you can type your line styling function
"function(){ makeDashed( opts ) } "
# here is the part that was in afterScript but moved to work with shiny
#see this article for help on dashed d3 lines
#http://www.d3noob.org/2013/01/making-dashed-line-in-d3js.html
,"
function makeDashed( opts ){
// select all the lines with d3 both main plot and focus
// see this article for help on dashed d3 lines
// http://www.d3noob.org/2013/01/making-dashed-line-in-d3js.html
d3.select('#' + opts.id).selectAll('.nv-linesWrap .nv-group')
.filter(function(g){return g.key== 'psavert'})
.selectAll('.nv-line')
.style('stroke-dasharray', ('3, 3'))
}
"
))
p7
I understand that this is a lot of Javascript to ask of a R user, so please let me know if any of this does not make sense.
var dasheddesign=['10,20','5,5' ,'30,30','20,10,5,5,5,10'];
d3.select('#chart1 svg')
.datum(data)
.call(chart)
.call(function(){
d3.select('#chart1')
.selectAll('.nv-line').each(function( d,i ){
d3.select(this).attr("stroke-dasharray",dasheddesign[i]);
});
});
No delay required
This works fine, but thy to Hide and then Unhide series from legend. dash style is gone :(
How can I change the axis titles of a plot generated with rCharts and the dimple.js library? For example:
library(rCharts)
data(mtcars)
mtcars.df <- data.frame( car = rownames(mtcars), mtcars )
d1 <- dPlot(x ="disp", y="mpg", groups=c("car", "cyl"), type ="point", data=mtcars.df)
d1$xAxis( type = "addMeasureAxis")
d1
The desired effect is to replace the variable name "disp" with a more complete piece of text as the axis title. I've tried adding arguments to the d1$xAxis() line like title="Displacement" and label="Displacement: but without success.
Sorry I just saw this. Thanks John for answering.
With rCharts, we can take advantage of the afterScript template to add this. If there is only one chart in the DOM, we can use John's example unmodified.
d1$setTemplate(
afterScript =
'
d3.selectAll(".axis.title")
.text(function () {
var t = d3.select(this).text();
if (t === "disp") {
return "Displacement";
} else if (t === "mpg") {
return "Miles Per Gallon";
} else {
return t;
}
});
'
)
Please let me know if this you would like an example with multiple charts in the DOM or this does not work for you. Thanks.
Dimple doesn't currently expose the titles, however it's coming in the next release. Once it does I'm sure the great guys behind the dimple implementation in rcharts will add them into the library. I'm not quite sure how this works with an R implementation but if you can run some Javascript once the chart is rendered you can modify the titles using some raw d3:
d3.selectAll(".axis.title")
.text(function () {
var t = d3.select(this).text();
return (t === "disp" ? "Displacement" : t);
});
If you want to extend this to replace a couple of titles you can do it with:
d3.selectAll(".axis.title")
.text(function () {
var t = d3.select(this).text();
if (t === "disp") {
return "Displacement";
} else if (t === "mpg") {
return "Miles Per Gallon";
} else {
return t;
}
});
I hope this helps.
Here is another way:
# devtools::install_github("rCharts", "ramnathv", ref = "dev")
library(rCharts)
data(mtcars)
mtcars.df <- data.frame( car = rownames(mtcars), mtcars )
d1 <- dPlot(x ="disp", y="mpg", groups=c("car", "cyl"), type ="point", data=mtcars.df)
d1$xAxis( type = "addMeasureAxis")
d1
d1$setTemplate(afterScript = "
<script>
myChart.draw()
myChart.axes[0].titleShape.text('Displacement')
myChart.axes[1].titleShape.text('Miles Per Gallon')
myChart.svg.append('text')
.attr('x', 40)
.attr('y', 20)
.text('Plot of Miles Per Gallon / Displacement')
.style('text-anchor','beginning')
.style('font-size', '100%')
.style('font-family','sans-serif')
</script>
")
d1
Screenshot:
Hat tip to Ramnath: R: interactive plots (tooltips): rCharts dimple plot: formatting axis
I have a radchart that is supposed to display two lines with separate Y values, but when it loads it displays two lines, but they both have Y values that they are supposed to display and the Y values of the other line in the chart
ie:
(Data set 1: 1,2,6,9)
(Data set 2: 1,4,6,7)
(Lines display like: 1-1-2-4-6-6-9-7 and 1-1-2-4-6-6-9-7)
my series mapping looks like this:
<telerik:SeriesMapping LegendLabel="Line Series 1" >
<telerik:SeriesMapping.SeriesDefinition>
<telerik:StackedLineSeriesDefinition />
</telerik:SeriesMapping.SeriesDefinition>
<telerik:ItemMapping FieldName="LineYValue" DataPointMember="YValue" />
</telerik:SeriesMapping>
<telerik:SeriesMapping LegendLabel="Line Series 2" >
<telerik:SeriesMapping.SeriesDefinition>
<telerik:StackedLineSeriesDefinition />
</telerik:SeriesMapping.SeriesDefinition>
<telerik:ItemMapping FieldName="LineYValue" DataPointMember="YValue" />
</telerik:SeriesMapping>
</telerik:RadChart.SeriesMappings>
and in my ViewModel I have the data loaded like so
if (salesAndQtyByHour != null)
{
STUFF.ChartModels.Charts tempChart1 = new ODA.ChartModels.Charts() { ChartTitle = "Sales And Quantity By Hour" };
foreach (var item in salesAndQtyByHour)
{
tempChart1.myCharts.Add(new STUFF.ChartModels.Chart() { LineName = "Quantity", LineXValue = item.MilitaryTime, LineYValue = item.QtyOfItems });
tempChart1.myCharts.Add(new STUFF.ChartModels.Chart() { LineName = "Basket", LineXValue = item.MilitaryTime, LineYValue = item.Basket });
}
SalesAndQtyByHour = tempChart1;
}
I'm pretty sure its the CollectionIndex, but I'm not sure how to implement it in my case, I'm relatively new to this job.
Any help would be appreciated.
There is ItemsSource property of the SeriesMapping object. You can send each individual dataset to the corresponding SeriesMapping. Article:
http://www.telerik.com/help/silverlight/radchart-populating-with-data-series-mapping-items-source.html