i have a chart like below
<canvas
height="100"
width="100"
baseChart
[chartType]="'bar'"
[datasets]="chartData"
[colors]="colors"
[labels]="chartLabels"
[options]="chartOptions"
[legend]="true"
(chartClick)="onChartClick($event)">
</canvas>
and has handle to it like
#ViewChild(BaseChartDirective)
public chart: BaseChartDirective;
This all works fine. except now i need to add a new chart on the same html page but i do not know how to create another handle to the another chart canvas. BaseChartDirective seems to be generic. how is it connecting to the canvas ?
You can use ViewChildren like this:
#ViewChildren(BaseChartDirective) charts: QueryList<BaseChartDirective>;
and then access the first and second chart like this:
public myMethod(): void {
// First canvas
const firstCanvas = this.charts.toArray()[0];
// Second canvas
const secondCanvas = this.charts.toArray()[1];
}
Please notice that
#ViewChildren(BaseChartDirective) charts: QueryList<BaseChartDirective>;
is exactly the same as
#ViewChildren(BaseChartDirective)
public charts: QueryList<BaseChartDirective>;
Related
Hi is there any way to pass jsx component to bindPopup function so I can push redux commands on button click?
pointToLayer={(
geoJsonPoint: Feature<Point, DeviceProperties>,
latlng,
) => {
const marker = L.marker(latlng);
marker.setIcon(
markerIcon({ variant: geoJsonPoint.properties.relation }),
);
const sddds = (
<div className="font-quicksand">
<h2>{geoJsonPoint.properties.id}</h2>
<h2>{geoJsonPoint.properties.name}</h2>
<p>{geoJsonPoint.properties.description}</p>
<p>{geoJsonPoint.properties.ownerId}</p>
<a
onClick={() => {
dispatch(setDevice(geoJsonPoint.properties));
}}
>
Open device details
</a>
</div>
);
marker.bindPopup(renderToString(sddds));
return marker;
}}
I know I can use react leaflet component but that way I cant pass props into every marker options (I mean marker as layer).
So this has been discussed a bit. There is an issue in the react-leaflet repo discussing this, whose conclusion is to simply use vanilla JS within the bindPopup method to create your popup. I don't like this solution at all, especially when you're trying to use very react oriented event handlers (like react-redux actions) from within a popup.
The question React-leaflet geojson onEachFeature popup with custom react component was asked, which you may have read, as you use react's renderToString method in your code. But as you've probably discovered, this does not maintain any interactivity or JS that your JSX may include. The answerer there came up with the idea of using a modal instead of a popup, but that doesn't exactly answer your question or truly using JSX in a popup based off of a point-layer geojson.
Ultimately, you will not be able to return JSX from the pointToLayer function that is interactive. I think this would be a nice feature that react-leaflet doesn't currently implement. Within the closure of the pointToLayer function, there's no good way to directly write fully functional JSX.
I played with this for a bit, trying to harness pointToLayer and save the feature of each iteration to state, and then render a Marker with Popup from that, but it got me thinking - why bother? Just ditch the GeoJSON component altogether and render your Markers and Popups directly from the JSON object. Like this:
{myGeoJson.features.map((feature, index) => {
return (
<Marker
key={index}
position={L.latLng(feature.geometry.coordinates.reverse())}
>
<Popup>
<button
onClick={() => { yourReduxAction() }}
>
Click meeee
</button>
</Popup>
</Marker>
);
})}
Working sandbox
In this way, you need to work a little harder by manually transforming your GeoJSON into Markers with Popups, but not nearly as hard as trying to bend over backwards by going from JSX (<GeoJSON />) to vanilla JS (pointToLayer) back to JSX (<Popup />).
These are two solutions I have come to and want to share if someone is having same problem.
My problem with using leaflet-react Popup component is that it will not pass geojson properties to marker layer when I just map over geojson object because react-leaflet Marker does not have api for feature like geojson layer does and I need to access those properties via marker layers in other parts of map.
Solution 1:
Use ReactDOM.render() inside pointToLayer method, react will show warning about pure functions but it will work. You just shoud not render imported component because it will complain about store and redux provider, instead paste component code inside render. If you want to avoid warnings create another function / hook and render code inside its useEffect() to container (div or something).
Here is example:
const popup = L.popup();
const marker = L.marker(latlng);
const container = L.DomUtil.create('div');
render(
<div>
<h2>{props.id}</h2>
<h2>{props.name}</h2>
<p>{props.description}</p>
<p>{props.ownerId}</p>
<a onClick={() => dispatch(setDevice(geoJsonPoint.properties))}></a>
</div>,
container,
);
popup.setContent(container);
marker.bindPopup(popup);
return marker;
With custom hook / function:
const useRenderPopup = (props) => {
const container = L.DomUtil('div');
const dispatch = useAppDispatch()
useEffect(() => {
render(
<div>
<h2>{props.id}</h2>
<h2>{props.name}</h2>
<p>{props.description}</p>
<p>{props.ownerId}</p>
<a onClick={() => dispatch(setDevice(props.geoJsonPoint.properties))}></a>
</div>,
container,
);
},[])
return container;
}
and just call this function like popup.setContent(useRenderPopup(someprop)), this way there will be no warning.
Solution 2:
Render everything static with renderToString() and other stuff that need to trigger redux update attach event listeners.
const popup = L.popup();
const marker = L.marker(latlng);
const link = L.DomUtil.create('a');
const container = L.DomUtil.create('div');
const content = <DeviceSummary {...geoJsonPoint.properties} />;
marker.setIcon(markerIcon({ variant: geoJsonPoint.properties.relation }));
link.addEventListener('click', () =>
dispatch(setDevice(geoJsonPoint.properties)),
);
link.innerHTML = 'Show device details';
container.innerHTML = renderToString(content);
container.appendChild(link);
popup.setContent(container);
marker.bindPopup(popup);
return marker;
Here DeviceSummary component is static so I render it as a string and later append link with redux callback added as event listener to it.
(both solutions except custom function example goes into pointToLatyer method inside geoJSON layer)
I'm implementing a fancybox into my project and I'm writing a script to automatically wrap an anchor around the images with the url to the image and a "data-fancybox" attribute to let the fancybox script do its thing. However, I'm only getting the url to the very first image, since they all share the same class. There is a dynamic figure id that seems to be the one to get.
My question is - how do I use this figure id to fetch the appropriate img src?
The html is like this:
<figure id="XXXXXXX">
<div>
<img src="image.jpg" />
</div>
</figure>
... other stuff ...
<figure id="YYYYYYY">
<div>
<img src="image2.jpg" alt="">
</div>
</figure>
My code right now is as follows (which works, but only returns the first image url):
$(document).ready(function() {
var src = $("figure img").attr("src");
var a = $("<a/>").attr( { href:src , "data-fancybox":"" } );
$("figure img").wrap(a);
});
I know I can use
var id = $("figure").attr("id");
to get the id I need, but I'm pretty new to coding so I'm not sure how I implement this and use it to get the correct url. Any help is appreciated!
If your goal is to make your images clickable, then you can do smth like this:
$('figure img').each(function() {
$(this).parent().css({cursor: 'pointer'}).attr('data-fancybox', 'gallery').attr('data-src', this.src);
});
DEMO - https://jsfiddle.net/1jznsL7x/
Tip: There is no need to create anchor elements, you can add data-fancybox and data-src attributes to any element and it will work automagically.
In my old leaflet application I used this code to rotate an icon and it worked perfectly.
Now, I'm trying to move the code to react-leaflet but cannot figure out how to apply this.
I know it should be possible via custom components, I tried to create some kind on RotatedMarker (based on Marker.js in the src), but as I'm new to all that, I could not make it work...
Can anyone point me in the right direction?
Thanks,
Alex
Ok. This is what I did to make it work. Not sure it should be done this way, but it seems to work.
export default class RotatedMarker extends Marker {
componentDidMount() {
super.componentDidMount();
this.leafletElement.setIconAngle(this.props.rotation);
}
componentWillUpdate(nextProps, nextState) {
if (nextProps.rotation) {
this.leafletElement.setIconAngle(nextProps.rotation);
}
}
}
I found another way, try constructing the icon like this:
var icon = L.divIcon({
iconSize: [20, 20],
iconAnchor: [10, 10],
className: 'yourClassName',
html: `<img
style="transform: rotate(80deg);"
height="20"
width="20"
src='path/to/icon'>`
})
And then add it to your marker:
<Marker position={position} icon={icon} />
I use primefaces 4.0 barchart, but i want to modify some default proprieties. This is my xhtml page
<p:barChart id="chart" value="#{marchebean.model}" legendPosition="ne" xaxisLabel="Année" yaxisLabel="Montant en DT" title="Factures payés par années" />
What i want to do
1.I want to separate between hundreds , thousands and millions in y axis by a space i.e changing the format of my numbers , i use NumberFormat.getNumberInstance(Locale.FRANCE).format(mynumber); in java but i don't know how to achieve this with charts in primefaces.
I want to display on the top of every bar the value setted in backing bean and change it's size.
Edit
Separation between thousand done, but i still don't know how display values above every bar or change their size. this is my new code
<p:barChart id="chart" value="#{marchebean.model}" extender="ext" style="height:600px;" legendPosition="ne" xaxisLabel="Année" yaxisLabel="Montant en DT" title="Factures payés par années" />
<script type="text/javascript">
function ext() {
this.cfg.axes.yaxis.tickOptions = {
formatString: "%'i"
};
this.cfg.seriesDefaults={
renderer: $.jqplot.BarRenderer,
pointLabels:{show:true}
},
$.jqplot.sprintf.thousandsSeparator = ' ';
}
</script>
First question
You can fulfill your need #1 using an extender.
Here's the related excerpt from Primefaces' manual regarding extenders:
3.14.15 Extender Chart API provide high level access to commonly used jqplot options however there are many more customization options
available in jqplot. Extender feature provide access to low level apis
to do advanced customization by enhancing the configuration object,
here is an example to increase shadow depth of the line series where
model's extender property is set to "ext".
<p:chart type="line" model="#{bean.model}" />
function ext() {
//this = chart widget instance
//this.cfg = options
this.cfg.seriesDefaults = {
shadowDepth: 5
};
}
Refer to jqPlot docs for available options.
Note: in Primefaces 4.0, the extender function name is provided as an attribute of the component tag, eg. <p:barChart extender="ext" .../>
To use whitespaces as thousands separator, use the following extender:
function ext() {
this.cfg.axes.yaxis.tickOptions = {
formatString: "%'i"
};
$.jqplot.sprintf.thousandsSeparator = ' ';
}
Second question
You will also manage to do it making some tweaking in your extender.
For example, the following piece of configuration will display values above every bar:
seriesDefaults: {
renderer: $.jqplot.BarRenderer,
pointLabels:{show:true}
},
And just use some CSS to change the size!
--
Zim
I am trying to create image on every xaxis in my highchart like the one in the attached image , but couldn't find the way for it
here is my code for the xaxis
chart.getXAxis(1).setOpposite(true).setLinkedTo(0).setType(Type.DATE_TIME)
.setTickInterval(6 * 3600 *1000).setGridLineWidth(1)
any idea how can i set some image to every xaxis in my chart
You will want to use the AxisLabelsFormatter. The content you "return" from this call should be the appropriate html you want to have rendered. So something like this work for the top axis:
chart.getXAxis().setLabels(
new XAxisLabels()
.setFormatter(new AxisLabelsFormatter() {
public String format(AxisLabelsData axisLabelsData) {
return '<img src="/path/to/image.gif" />';
}
})
);