HighChart with more than 50 data points breaks - javascript

I am trying to create a bubble chart using the JS HighChart in Angular2+. Whenever there are more than 50 data points (bubbles), the graph breaks. There are the correct number of bubbles in the correct positions (x,y plots) with all different colors but the sizes are all the same even though the z-values are all different. (I am outputing the z-values in a tooltip and the z-values are accurate)
This function is how I am passing in data to the high-chart configuration.
setSeries() {
this.objData = []
this.Data.forEach(element => {
var x= element['xVal'];
var y = element['yVal'];
var z = element['zVal'].toFixed(0);
var name = element['seriesName'].trim();
var newData =[{
x:x,
y:y,
z:+z,
}]
// SetSeriesData is how i am creating the obj to pass into series=[] in highchart configuration
if(i<50) //If I comment this condition, the graph breaks. Right now, the graph is working properly
this.setSeriesData(sumData, name, this.objData)
i++
})
this.options.series = this.objData;
this.generateChart();
}
This is my setSeriesData function.
setSeriesData(graphData: any, dataName: any, objData: any){
var obj = {};
obj['name'] = dataName;
obj['data'] = graphData;
obj['events'] = {click: function(e) {
//takes me to another link
}};
objData.push(obj)
}
In the above function, I configured the chart so that when you click the bubble, it takes you to another page. When the data points >50, this click functionality is not working either. In addition, the fillOpacity is not correct.
Just a few things to point out
1. I am using Angular 2+
2. The discovered issues are, fillOpacity, click, and size based on z-value.
3. It works perfectly when the data points are less than 50
How can I fix this?

Related

Cesium JS - Accessing primitive attributes without using id

So essentially, I have a function that draws a colored rectangle at every globe coordinate point via coordinate and color arrays. (Frequency means new rectangle every x coordinates)
//Given an array of coordinates, respective colors, and level of detail,
//Draws heatmap on the globe
function DrawMapGivenArrays(CoordinateArray, Colors, frequency)
{
var instances = [];
for(var i = 0; i < CoordinateArray.length; i++)
{
var Cartesian1 = new Cesium.Cartesian3.fromDegrees(CoordinateArray[i].lon,
CoordinateArray[i].lat);
var Cartesian2 = new Cesium.Cartesian3.fromDegrees(CoordinateArray[i].lon+frequency,
CoordinateArray[i].lat-frequency);
var CartesianArray = Array();
CartesianArray.push(Cartesian1);
CartesianArray.push(Cartesian2);
var newPrim = new Cesium.GeometryInstance
({
geometry : new Cesium.RectangleGeometry
({
rectangle : Cesium.Rectangle.fromCartesianArray(CartesianArray),
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
}),
attributes :
{
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Colors[i])
},
id: "Rectangle" + i,
});
instances.push(newPrim);
numberOfRectangles++;
}
var primitive = new Cesium.Primitive
({
releaseGeometryInstances : false,
geometryInstances : instances,
appearance : new Cesium.PerInstanceColorAppearance(),
});
scene.primitives._primitives[1] = primitive;
}
That works fine.. After I draw the map, I'm using this small function below to individually edit the color of one rectangle. (I call this for every rectangle to change all of them).
//Changes the color of a rectangle primitive given its unique id and a color value
function setPrimitiveRectangle(id, color)
{
var CesiumColor = Cesium.ColorGeometryInstanceAttribute.toValue(color);
scene.primitives._primitives[1].getGeometryInstanceAttributes(id).color = CesiumColor; //this line is 10x slower for every instance after it runs the 1st time
//".getGeometryInstanceAttributes(id)" This specific phrase runs 10x slower after 1st instance
}
That works as well. But, for some reason, it has issues.
For example, when I re-color all of the rectangles the first time, it runs very fast. However, every time I re-fun that function again after the first time, it's 10x slower. I narrowed it down to the phrase that was giving me problems( ".getGeometryInstanceAttributes(id)" ).
I tried to circumvent calling the get function by modifying color values directly with this: (Where i is iterating over every rectangle).
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[0] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[1] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[2] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[3] = 0;
Once I do this, I can check in chrome and see that the values located at those areas changed, however, the colors of the rectangles do not update.
I don't understand why ".getGeometryInstanceAttributes(id)" runs 10x slower after it's called the first time, and why I cannot directly modify viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[0].
Thanks

Bokeh: CustomJS TextInput callback to adjust x axis range

I'm trying to make a web page that has a plot powered by an AjaxDataSource object. However, I'd like to have a TextInput widget that can be used to changed the xrange of this plot. Below is a snippet:
source = AjaxDataSource(data={"time": [], "temperature": [], "id": []},
data_url='http://localhost:6543/AJAXdata',
polling_interval=100,
mode='append')
livePlot = figure(x_axis_type="datetime",
x_range=[startDt, endDt],
y_range=(0,25),
y_axis_label='Temperature (Celsius)',
title="Sea Surface Temperature at 43.18, -70.43",
plot_width=800)
livePlot.line("time", "temperature", source=source)
....
updateStartJS = CustomJS(args=dict(xrange=livePlot.x_range), code="""
var startStr = cb_obj.value
alert(startStr)
var newStartMilliSeconds = Date.parse(startStr)
alert(newStartMilliSeconds)
alert(xrange)
alert(xrange.start)
xrange.start = newStartMilliSeconds
alert(xrange.start)
xrange.change.emit();
""")
startInput = TextInput(value=startDt.strftime(dateFmt), callback=updateStartJS)
See this file and the bokeh_ajax() function for the complete implementation: https://github.com/hhprogram/PyramidSite/blob/master/webgraphing/views/ajaxView.py
When I run it and change the corresponding 'Start' textInput box. The CustomJS runs accordingly and per the alerts I have seen that it is capturing the correct new data (assuming you put in an ISO Formatted date like YYYY-mm-dd) but it fails to update the plot axis range (ie the plot doesn't change at all). How would I implement this? (I want to maintain the plots to have underlying AjaxDataSources as well and not use bokeh server - I already know how to implement this type of axis change functionality if running a bokeh server.)
For anyone curious, found my issue. Think the main problem was I was not putting the widget which I wanted to use to control the plot xrange and the actual plot itself within the same layout object. Therefore, when I was calling components on the plot object it didn't include the widget. Then when I included the widget with the plot it worked. See below updates and the updated github repo:
(credit to this post for significantly helping me: Flask + Bokeh AjaxDataSource)
complete file: https://github.com/hhprogram/PyramidSite/blob/master/webgraphing/views/ajaxView.py)
code snippet:
source = AjaxDataSource(data={"time": [], "temperature": [], "id": []},
data_url='http://localhost:6543/AJAXdata',
polling_interval=100,
mode='append')
livePlot = figure(x_axis_type="datetime",
x_range=[startDt, endDt],
y_range=(0,25),
y_axis_label='Temperature (Celsius)',
title="Sea Surface Temperature at 43.18, -70.43",
plot_width=800)
livePlot.line("time", "temperature", source=source)
jsResources = INLINE.render_js()
cssResources = INLINE.render_css()
updateStartJS = CustomJS(args=dict(plotRange=livePlot.x_range), code="""
var newStart = Date.parse(cb_obj.value)
plotRange.start = newStart
plotRange.change.emit()
""")
updateEndJS = CustomJS(args=dict(plotRange=livePlot.x_range), code="""
var newEnd = Date.parse(cb_obj.value)
plotRange.end = newEnd
plotRange.change.emit()
""")
startInput = TextInput(value=startDt.strftime(dateFmt), title="Enter Date in format: YYYY-mm-dd")
startInput.js_on_change('value', updateStartJS)
endInput = TextInput(value=endDt.strftime(dateFmt), title="Enter Date in format: YYYY-mm-dd")
endInput.js_on_change('value', updateEndJS)
textWidgets = row(startInput, endInput)
# NOTE: this is important. Need to have the widgets and plot within same object that is the argument for components() method
layout = column(textWidgets, livePlot)
script, div = components(layout)

The scale on the axis doesn't automatically narrow

I'm using chart.js but on some and only some of the graphs it creates the y-axis scale goes from 0-100 when a more appropriate scale might be 80-100. This means all the lines are bunched up at the top.
You can see what I mean if you visit mbi.dajubox.com and select '14 days' under waiting times. When the results come up beneath click the first entry (Calderdale And Huddersfield NHS Foundation Trust) and the graph appears. But the lines are bunched at the top.
If I go down to number 15 though (Stockport NHS Foundation Trust) it scales the axis ok.
The code that generates them is the same
var ctx = document.getElementById("myChart_"+provID).getContext("2d");
var myLineChart = new Chart(ctx).Line(data, {bezierCurve: false, multiTooltipTemplate: "<%= datasetLabel %> - <%= value %>"});
Can any one help me out?
This is because in your data you receive null values to display in the chart. Chartjs's min function is just a wrapper for the Math.min which will treat null as 0.
A fix for this can be to override the helper function calculateScaleRange
Just declare this after you have Chart.js (or apply the small change straight to your Chart.js)
Chart.helpers.calculateScaleRange = function (valuesArray, drawingSize, textSize, startFromZero, integersOnly) {
//Set a minimum step of two - a point at the top of the graph, and a point at the base
var minSteps = 2,
maxSteps = Math.floor(drawingSize / (textSize * 1.5)),
skipFitting = (minSteps >= maxSteps);
var maxValue = Chart.helpers.max(valuesArray),
minValue = Chart.helpers.min(valuesArray.map(function(value){
//using map to create a new array where all nulls are mapped to Infinity so they do not pull the result down to 0
return value === null ? Infinity: value;
}));
................ //lots more code that is part of calculateScaleRange
here is a full example http://jsfiddle.net/leighking2/L9kLxpe1/

Chart.js zeros handling

I'm working with chart.js and to render a doughnut chart. I want to set the initial chart total value to zero so it can render a full " empty" chart. When I instatiate the chart with zeros it does not render. I cannot find how it handle zeros in the developer documentation.
var kPoints = 000;
var mPoints = 000;
var tPoints = 000;
var cPoints = 000;
var doughnutData = [ {
value : kPoints,
color : "#FF8000"
}, {
value : mPoints,
color : "#99CC00"
}, {
value : tPoints,
color : "#0099CC"
}, {
value : cPoints,
color : "#333333"
}, ];
var ctx = $("#profileChart").get(0).getContext("2d");
var myDoughnut = new Chart(ctx).Doughnut(doughnutData);
From reading the source code for Chart.js I've found that the it tries to sum each of the value fields in its datasource before rendering the chart (see the use of segmentTotal here).
To workaround this, use null for all the values and set one (or more) of the data points to a very small, near zero value. I've used a float notation here for one of the values:
var kPoints = null;
var mPoints = null;
var tPoints = null;
var cPoints = 1e-10;
After that, the example below re-renders the chart (after a 3 second delay) with different data values to show a case of the values updating from the default "dark" chart to a filled out version:
setTimeout(function () {
// Generate a new, random value for each of the data points
doughnutData.forEach(function (item) {
item.value = Math.floor((Math.random() * 10) + 1);
});
var ctx = $("#profileChart").get(0).getContext("2d");
var myDoughnut = new Chart(ctx).Doughnut(doughnutData);
}, 3000);
JSFiddle Example: http://jsfiddle.net/MasterXen/6S9DB/3/
Keep a running total of the values when building the doughnut data.
If there are zero data points, or the total value of all data points is zero, then simply inject an extra dummy point with a label like "No Data" along with an either imperceptible (near-zero) value or a dummy value like 1. In either case, you'll end up with a valid chart with a single category like "No Data".

NVD3.js Given X-Axis Value, Get all Lines' Y-Axis Values

I'm using the line-with-focus chart ( View Finder ) example in nvd3. That means there's 3 or 4 lines ( series ) being drawn on the graph. When i hover over any of the lines I want to get back all the y-values for all lines of that given x-axis position ( for the most part these will be interpolated y-values per line ).
I see in the nv.models.lineWithFocusChart source code that using a callback for the elementMouseover.tooltip event I can get my data's x-value back for the data points on the line.
The closest part of the source code that does what i want is with the interactiveGuideline code for the lineChart examples. However, i don't want to create a <rect> overlay with elementMousemove interaction. I think i can modify this code to filter my data and get each line's y-value, but I'm sure there's an easier way I'm not seeing.
I think I'm on the right track, but just wondering if someone had this need before and found a quicker route than the rabbit hole I'm about jump in.
Thanks for feedback
This is the basic functionality you're looking for, it still needs a bit of finesse and styling of the tooltips. (Right now the tooltip blocks the view of the points...)
Key code to call after the drawing the chart in (for example, within the nv.addGraph function on the NVD3 live code site):
d3.selectAll("g.nv-focus g.nv-point-paths")
.on("mouseover.mine", function(dataset){
//console.log("Data: ", dataset);
var singlePoint, pointIndex, pointXLocation, allData = [];
var lines = chart.lines;
var xScale = chart.xAxis.scale();
var yScale = chart.yAxis.scale();
var mouseCoords = d3.mouse(this);
var pointXValue = xScale.invert(mouseCoords[0]);
dataset
.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled;
})
.forEach(function(series,i) {
pointIndex = nv.interactiveBisect(series.values, pointXValue, lines.x());
lines.highlightPoint(i, pointIndex, true);
var point = series.values[pointIndex];
if (typeof point === 'undefined') return;
if (typeof singlePoint === 'undefined') singlePoint = point;
if (typeof pointXLocation === 'undefined')
pointXLocation = xScale(lines.x()(point,pointIndex));
allData.push({
key: series.key,
value: lines.y()(point, pointIndex),
color: lines.color()(series,series.seriesIndex)
});
});
/*
Returns the index in the array "values" that is closest to searchVal.
Only returns an index if searchVal is within some "threshold".
Otherwise, returns null.
*/
nv.nearestValueIndex = function (values, searchVal, threshold) {
"use strict";
var yDistMax = Infinity, indexToHighlight = null;
values.forEach(function(d,i) {
var delta = Math.abs(searchVal - d);
if ( delta <= yDistMax && delta < threshold) {
yDistMax = delta;
indexToHighlight = i;
}
});
return indexToHighlight;
};
//Determine which line the mouse is closest to.
if (allData.length > 2) {
var yValue = yScale.invert( mouseCoords[1] );
var domainExtent = Math.abs(yScale.domain()[0] - yScale.domain()[1]);
var threshold = 0.03 * domainExtent;
var indexToHighlight = nv.nearestValueIndex(
allData.map(function(d){ return d.value}), yValue, threshold
);
if (indexToHighlight !== null)
allData[indexToHighlight].highlight = true;
//set a flag you can use when styling the tooltip
}
//console.log("Points for all series", allData);
var xValue = chart.xAxis.tickFormat()( lines.x()(singlePoint,pointIndex) );
d3.select("div.nvtooltip:last-of-type")
.html(
"Point: " + xValue + "<br/>" +
allData.map(function(point){
return "<span style='color:" + point.color +
(point.highlight? ";font-weight:bold" : "") + "'>" +
point.key + ": " +
chart.yAxis.tickFormat()(point.value) +
"</span>";
}).join("<br/><hr/>")
);
}).on("mouseout.mine", function(d,i){
//select all the visible circles and remove the hover class
d3.selectAll("g.nv-focus circle.hover").classed("hover", false);
});
The first thing to figure out was which objects should I bind the events to? The logical choice was the Voronoi path elements, but even when I namespaced the event names to avoid conflict the internal event handlers nothing was triggering my event handling function. It seems that a parent <g> event captures the mouse events before they can reach the individual <path> elements. However, it works just fine if instead I bind the events to the <g> element that contains the Voronoi paths, and it has the added benefit of giving me direct access to the entire dataset as the data object passed to my function. That means that even if the data is later updated, the function is still using the active data.
The rest of the code is based on the Interactive Guideline code for the NVD3 line graphs, but I had to make a couple important changes:
Their code is inside the closure of the chart function and can access private variables, I can't. Also the context+focus graph has slightly different names/functionality for accessing chart components, because it is made up of two charts. Because of that:
chart in the internal code is chart.lines externally,
xScale and yScale have to be accessed from the chart axes,
the color scale and the x and y accessor functions are accessible within lines,
I have to select the tooltip instead of having it in a variable
Their function is called with custom event as the e parameter that has already had the mouse coordinates calculated, I have to calculate them myself.
One of their calculations uses a function (nv.nearestValueIndex) which is only initialized if you create an interactive layer, so I had to copy that function definition into mine.
I think that about covers it. If there's anything else you can't follow, leave a comment.

Categories

Resources