I want to set the type of graph programaticaly.
So, instead of using, for example, this in seriesDefaults:
renderer:$.jqplot.BarRenderer,
I want to use renderer:$.jqplot.graphtype, where graphtype has been set to BarRenderer, LineRenderer etc.
But it does not work in whatever format I use graphtype (always defaults to default line graph). Advice on passing this value appreciated.
thanks
Setting an option like this renderer:$.jqplot.BarRenderer in your options already is a programmatical way of doing it.
Presumably, you're trying to change this value depending on a text variable (as opposed to a reference to an object like $.jqplot.BarRenderer).
You don't have to define this when you define your options. You could do this later, based on a default set of options. For example:
var myDefaultOptions = {
seriesDefaults:{
pointLabels: { show: true }
},
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: ticks
}
},
highlighter: { show: false }
}
// ...
// Note: $.extend(true, ...) for a deep copy, if you're re-using this later.
var myOptions = $.extend(true, {}, myDefaultOptions);
if (graphType == 'bars') {
myOptions.seriesDefaults.renderer = $.jqplot.BarRenderer;
} else if (graphType == '...') {
// Other renderer
}
var plot = $.jqplot('chart1', data, myOptions);
Using this might also give you the opportunity to change other settings that may make more sense depending on the graph type (whether it makes sense to zoom, highlight, ...).
If you want to do this for specific series, you should first create an empty series: {} in your default options, it will make it easier to set individual options there (accessing each series with a number).
Related
I'm aware of binding a pop-up to ESRI's L.esri.DynamicMapLayer here. The following code below is successful.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindPopup(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
var obj = featureCollection.features[0].properties;
var val = obj['Pixel Value'];
var lat = featureCollection.features[0].geometry.coordinates[1];
var lon = featureCollection.features[0].geometry.coordinates[0];
new L.responsivePopup({
autoPanPadding: [10, 10],
closeButton: true,
autoPan: false
}).setContent(parseFloat(val).toFixed(2)).setLatLng([lat, lon]).openOn(map);
}
});
}
});
But rather than a click response I am wondering as to whether you can mouseover using bindTooltip instead on a dynamic map. I've looked at the documentation for L.esri.DynamicMapLayer which says it is an extension of L.ImageOverlay. But perhaps there is an issue outlined here that I'm not fully understanding. Maybe it is not even related.
Aside, I've been testing multiple variations of even the simplest code to get things to work below but have been unsuccessful. Perhaps because this is asynchronous behavior it isn't possible. Looking for any guidance and/or explanation(s). Very novice programmer and much obliged for expertise.
$.ajax({
type: 'GET',
url: url + '?f=json',
data: { layer: fooType },
dataType: 'json',
success: function(json) {
var foo_layer = fooLayers[fooType].layers;
foo = L.esri.dynamicMapLayer({
url: url,
layers: [foo_layer],
transparent: true
}).addTo(map).bringToFront();
foo.bindTooltip(function(error, featureCollection) {
if (error || featureCollection.features.length === 0) {
return false;
} else {
new L.tooltip({
sticky: true
}).setContent('blah').setLatLng([lat,lng]).openOn(map);
}
});
}
});
Serendipitously, I have been working on a different problem, and one of the byproducts of that problem may come in handy for you.
Your primary issue is the asynchronous nature of the click event. If you open up your map (the first jsfiddle in your comment), open your dev tools network tab, and start clicking around, you will see a new network request made for every click. That's how a lot of esri query functions work - they need to query the server and check the database for the value you want at the given latlng. If you tried to attach that same behavior to a mousemove event, you'll trigger a huge number of network requests and you'll overload the browser - bad news.
One solution of what you can do, and its a lot more work, is to read the pixel data under the cursor of the image returned from the esri image service. If you know the exact rgb value of the pixel under the cursor, and you know what value that rgb value corresponds to in the map legend, you can achieve your result.
Here is a working example
And Here is the codesandbox source code. Don't be afraid to hit refresh, CSB is little wonky in the way it transpiles the modules.
What is happening here? Let's look step by step:
On map events like load, zoomend, moveend, a specialized function is fetching the same image that L.esri.dynamicMapLayer does, using something called EsriImageRequest, which is a class I wrote that reuses a lot of esri-leaflet's internal logic:
map.on("load moveend zoomend resize", applyImage);
const flashFloodImageRequest = new EsriImageRequest({
url: layer_url,
f: "image",
sublayer: "3",
});
function applyImage() {
flashFloodImageRequest
.fetchImage([map.getBounds()], map.getZoom())
.then((image) => {
//do something with the image
});
}
An instance of EsriImageRequest has the fetchImage method, which takes an array of L.LatLngBounds and a map zoom level, and returns an image - the same image that your dynamicMapLayer displays on the map.
EsriImageRequest is probably extra code that you don't need, but I happen to have just run into this issue. I wrote this because my app runs on a nodejs server, and I don't have a map instance with an L.esri.dynamicMapLayer. As a simpler alternative, you can target the leaflet DOM <img> element that shows your dynamicMapLayer, use that as your image source that we'll need in step 2. You will have to set up a listener on the src attribute of that element, and run the applyImage in that listener. If you're not familiar with how leaflet manages the DOM, look into your elements tab in the inspector, and you can find the <img> element here:
I'd recommend doing it that way, and not the way my example shows. Like I said, I happened to have just been working on a sort-of related issue.
Earlier in the code, I had set up a canvas, and using the css position, pointer-events, and opacity properties, it lays exactly over the map, but is set to take no interaction (I gave it a small amount of opacity in the example, but you'd probably want to set opacity to 0). In the applyImage function, the image we got is written to that canvas:
// earlier...
const mapContainer = document.getElementById("leafletMapid");
const canvas = document.getElementById("mycanvas");
const height = mapContainer.getBoundingClientRect().height;
const width = mapContainer.getBoundingClientRect().width;
canvas.height = height;
canvas.width = width;
const ctx = canvas.getContext("2d");
// inside applyImage .then:
.then((image) => {
image.crossOrigin = "*";
ctx.drawImage(image, 0, 0, width, height);
});
Now we have an invisible canvas who's pixel content is exactly the same as the dynamicMapLayer's.
Now we can listen to the map's mousemove event, and get the mouse's rgba pixel value from the canvas we created. If you read into my other question, you can see how I got the array of legend values, and how I'm using that array to map the pixel's rgba value back to the legend's value for that color. We can use the legend's value for that pixel, and set the popup content to that value.
map.on("mousemove", (e) => {
// get xy position on cavnas of the latlng
const { x, y } = map.latLngToContainerPoint(e.latlng);
// get the pixeldata for that xy position
const pixelData = ctx.getImageData(x, y, 1, 1);
const [R, G, B, A] = pixelData.data;
const rgbvalue = { R, G, B, A };
// get the value of that pixel according to the layer's legend
const value = legend.find((symbol) =>
compareObjectWithTolerance(symbol.rgbvalue, rgbvalue, 5)
);
// open the popup if its not already open
if (!popup.isOpen()) {
popup.setLatLng(e.latlng);
popup.openOn(map);
}
// set the position of the popup to the mouse cursor
popup.setLatLng(e.latlng);
// set the value of the popup content to the value you got from the legend
popup.setContent(`Value: ${value?.label || "unknown"}`);
});
As you can see, I'm also setting the latlng of the popup to wherever the mouse is. With closeButton: false in the popup options, it behaves much like a tooltip. I tried getting it to work with a proper L.tooltip, but I was having some trouble myself. This seems to create the same effect.
Sorry if this was a long answer. There are many ways to adapt / improve my code sample, but this should get you started.
I have a Kendo UI chart with some series data. When I update the series, the chart data does not change. I see from the documentation that it needs to be wired to a datasource. But the datasource property is not available in Angular tag.
HTML
<kendo-chart
[series]="chartConfig.series"
[valueAxis] ="chartConfig.valueAxes"
[legend]="chartConfig.legend"
[title]="chartConfig.title"
[tooltip]="chartConfig.tooltip"
(seriesClick)="onSeriesClick($event)"
(drag)="onDrag($event)"
(dragStart)="dragStart($event)"
(dragEnd)="dragEnd($event)">
</kendo-chart>
Typescript
public chartConfig = {
title: {
text: 'Insight'
},
legend: {
position: 'bottom'
},
series: [], //Some data
valueAxes: [], //Some data
categoryAxis: {
categories: [],
axisCrossingValues: [24, 24, 0],
justified: true
},
tooltip: {
visible: true,
format: '{0}',
template: '#= category #/03: #= value #'
}
};
When I have an updated value, then I update the series like this.
this.chartConfig.series[seriesIndex].data[xAxisIndex] = parseInt(updatedValue.Value);
But the Chart doesn't get updated.
There is no datasource directive, you don't need anything like that. The problem is Angular not picking up changes to this.chartConfig.series.
Angular change detection checks for reference changes, not simply value changes. The simplest solution for this problem is after whatever updates you perform, change the reference of this.chartConfig.series like so:
this.chartConfig.series[seriesIndex].data[xAxisIndex] = parseInt(updatedValue.Value);
this.chartConfig.series = [...this.chartConfig.series];
That final line basically creates a new array with the same elements as the original and changes the reference of this.chartConfig.series to point to the newly created array. This makes it more likely to be picked up by Angular's change detection.
I'm using Keen.io ("version": "3.4.1") JavaScript SDK, along with their integration with C3.js, to produce a donut graph by using the code below. However, I don't want percentages, but rather absolute numbers. i.e. Not 25%, but 7.
From reading the docs and looking at examples (see "var c3gauge") and example, I thought you could modify the output by applying the chartOptions. That doesn't seem to be working. At this point, I feel like I'm doing something stupid I'm just not catching.
How do I display absolute values in my donut, not percentages?
var c3donut = new Keen.Dataviz()
.library('c3')
.chartType('donut')
.el(document.getElementById(elem))
.title("Awesome Sauce")
.parseRawData(data)
.chartOptions({
donut: {
label: {
format: function (value) {
console.log("I never fire, why?");
return value;
}
}
}
})
.render();
This is possible with keen-dataviz.js library. I've created a working example here: https://jsfiddle.net/bx9efr4h/1/
Here's part of the code that made it work:
var chart = new Keen.Dataviz()
.el('#chart')
.type("donut")
.chartOptions({
donut: {
label: {
format: function(value) {
return value;
}
}
}
})
.prepare();
keen-js works a little differently because c3.js is not the default dataviz library for it. This is likely why it isn't working like expected for you.
I need to create a graph where the edge width is based on one of the edge property values. By looking at https://github.com/cytoscape/cytoscape.js/wiki/StyleObject, I see Cytoscape has a discreteMapper/passthroughMapper/continuousMapper but none of these let me access the edge properties. However, looking at the source code there's also a customMapper, which based on previous Cytoscape documentation(http://cytoscapeweb.cytoscape.org/documentation/mappers) would allow me to access the edge properties and return the width based on them. How do I do this? Inside the style object I tried:
...
width: { customMapper: { functionName: "widthMapper" } },
...
and
var widthMapper = function(data)
{
console.log(data);
};
before the initialization call. By setting a breakpoint to the function I see it never gets called. What am I doing wrong, do I need to add the mapper function somewhere in the Cytoscape object so it can see it?
the right syntax was
...
width: { customMapper: widthMapper },
...
I am trying to turn pointLabels on and off programmatically. I thought it would work something like this:
var data_ = [[1,1],[2,5],[4,9]];
var graph = $.jqplot(id_graph, [data_], {
series:[{pointLabels: { show:true } }]
}
);
graph.series[0].pointLabels.show=false;
graph.replot();
However, this still displays the point labels.
Thanks for any help!
althoug this post is old I found a solution for the problem:
var data_ = [[1,1],[2,5],[4,9]];
var graph = $.jqplot(id_graph, [data_], {
series:[{pointLabels: { show:true } }]
}
);
graph.series[0].plugins.pointLabels.show=false;
graph.replot();
Instead of using
graph.series[0].pointLabels.show=false;
use
graph.series[0].plugins.pointLabels.show=false;
In my case this worked.
Adding to Boro's answer, if you want to toggle the marker on a single series, it would be quicker to do:
graph.drawSeries({markerOptions:{show:false}},seriesIndex); //redraw single series
Calls to replot can be expensive with a large number of series.
Revved fiddle here.
I think what you want is actually showMarker option. Since in this code you are not setting point labels therefore they will never be show. The showMarker will let you switch the dots of the graph on/off.
Is that what you are in fact after? Otherwise please provide an example that you use.
Here is a sample made for a similar issue.
Please see this sample. There on the button click the change of makers visibility occurs.
Update:
This sample shows the solution, which uses the approach presented above, i.e. re-plotting the plot while changing the 'pointLabels' new parameter.
jQuery(document).ready(function () {
var data = [
[1, 1],
[2, 5],
[4, 9]
];
var graph;
var isShowPointLabels = true;
function makePlot(showPointLabels) {
graph = $.jqplot("chart", [data], {
series: [{
pointLabels: {
show: showPointLabels
}
}]
});
}
makePlot(isShowPointLabels);
$("#click").click(function () {
isShowPointLabels = !isShowPointLabels;
makePlot(isShowPointLabels);
graph.replot();
});
});
In this case I couldn't figure out how to use drawSeries(...) to re-plot just a single series, as #Mark shows for marker, which would be a good practice to do here.