How to add css style to custom image node in JavaScript InfoVis ToolKit - infovis

I have created custom nodes for my force directed InfoVis graph in which I display a user's image. I want to now add style to the image, such as adding a border and making it a circle. I tried adding css class as follows, but it's not working.
img.className = myClass;
here's my custom node code:
//Custom nodes
$jit.ForceDirected.Plot.NodeTypes.implement({
'customImage':
{
'render': function (node, canvas)
{
var ctx = canvas.getCtx();
var img = new Image();
var pos = node.getPos();
img.onload = function ()
{
ctx.drawImage(img, pos.x - 16, pos.y - 16);
}
var n = _nodes[node.id];
if (n && n.imageUrl)
{
var size = 52;
var url = n.imageUrl.replace("{width}", size).replace("{height}", size);
img.src = url;
img.className = myClass;
}
else
{
img.src = '../Images/UserNoImage.png';
}
},
'contains': function (node, pos)
{
var npos = node.pos.getc(true),
dim = node.getData('dim');
return this.nodeHelper.square.contains(npos, pos, dim);
}
}
});

You can apply CSS styles to the html elements. The custom node which you have defined is simply drawing image on the canvas. The node does not correspond to any html element.
Hence before you call function drawImage you should make sure that the image is customized to your requirement, only then you call drawImage. In this case infovis does not know anything about css, all that it does is call drawImage which simply draws an image on the canvas.
Thus, the question you should tackle is, how do you apply css to the image so that its customized to your requirement. And this question is independent of infovis.

Related

SwiftUI ScrollView to PDF

Currently, I am using this code to create a PDF, but it only creates a PDF of the part of the page the user is looking at, and not the entire scroll view. How can I create a multi-page PDF to get the entire scroll view?
func exportToPDF(width:CGFloat, height:CGFloat, airplane: String, completion: #escaping (String) -> Void) {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let outputFileURL = documentDirectory.appendingPathComponent("smartsheet-\(airplane.afterchar(first: "N")).pdf")
//Normal with
let width: CGFloat = width
//Estimate the height of your view
let height: CGFloat = height
let charts = SmartSheet().environmentObject(Variables())
let pdfVC = UIHostingController(rootView: charts)
pdfVC.view.frame = CGRect(x: 0, y: 0, width: width, height: height)
//Render the view behind all other views
let rootVC = UIApplication.shared.windows.first?.rootViewController
rootVC?.addChild(pdfVC)
rootVC?.view.insertSubview(pdfVC.view, at: 0)
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: width, height: height))
DispatchQueue.main.async {
do {
try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
context.beginPage()
rootVC?.view.layer.render(in: context.cgContext)
})
UserDefaults.standard.set(outputFileURL, forKey: "pdf")
UserDefaults.standard.synchronize()
print("wrote file to: \(outputFileURL.path)")
completion(outputFileURL.path)
} catch {
print("Could not create PDF file: \(error.localizedDescription)")
}
pdfVC.removeFromParent()
pdfVC.view.removeFromSuperview()
}
}
I just posted this answer to another question as well.
https://stackoverflow.com/a/74033480/14076779
After attempting a number of solutions to creating a pdf from a SwiftUI ScrollView and landed on this implementation. It is by no means an implementation that is pretty, but it seems to work.
M use case is that I present a modal to capture a user's signature and when the signature is collected, the content of the current screen is captured. There is no pagination, just a capture of the content currently rendered to the width of the screen and as much length as is required by the view.
The content consists of an array of cards that are dynamically generated, so the view has no awareness of the content and cannot calculate the height. I have not tested on a list, but I imagine it would provide similar results.
struct SummaryTabView: View {
// MARK: View Model
#ObservedObject var viewModel: SummaryTabViewModel
// MARK: States and Bindings
#State private var contentSize: CGSize = .zero // Updated when the size preference key for the overlay changes
#State private var contentSizeForPDF: CGSize = .zero // Set to match the content size when the signature modal is displayed.
// MARK: Initialization
init(viewModel: SummaryTabViewModel) {
self.viewModel = viewModel
}
// MARK: View Body
var body: some View {
ScrollView {
content
.background(Color.gray)
// Placing the a geometry reader in the overlay here seems to work better than doing so in a background and updates the size value for the size preference key.
.overlay(
GeometryReader { proxy in
Color.clear.preference(key: SizePreferenceKey.self,
value: proxy.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) {
// This keeps the content size up-to-date during changes to the content including rotating.
contentSize = $0
}
// There are issues with the sizing the content when the orientation changes and a modal view is open such as when collecting the signature. If the iPad is rotated when the modal is open, then the content size is not being set properly. To avoid this, when the user opens the modal for signing, the content size for PDF generation is set to the current value since the signature placeholder is already present so the content size will not change.
.onReceive(NotificationCenter.default.publisher(for: .openingSignatureModal)) { _ in
self.contentSizeForPDF = contentSize
}
}
// MARK: Subviews
var header: SceneHeaderView {
SceneHeaderView(viewModel: viewModel)
}
// Extracting this view into a variable allows grabbing the screenshot when the customer signature is collected.
var content: some View {
LazyVStack(spacing: 16) {
header
ForEach(viewModel.cardViewModels) { cardViewModel in
// Each card needs to be in a VStack with no spacing or it will end up having a gap between the header and the card body.
VStack(spacing: 0) {
UICardView(viewModel: cardViewModel)
}
}
}
.onReceive(viewModel.signaturePublisher) {
saveSnapshot(content: content, size: contentSizeForPDF)
}
}
// MARK: View Functions
/// Creates and saves a pdf data file to the inspection's report property. This is done asynchronously to avoid odd behavior with the graphics renering process.
private func saveSnapshot<Content: View>(content: Content, size: CGSize) {
let pdf = content
.frame(width: size.width, height: size.height)
.padding(.bottom, .standardEdgeSpacing) // Not sure why this padding needs to be added but for some reason without it the pdf is tight to the last card on the bottom. It appears that extra padding is being added above the header, but not sure why.
.toPDF(format: self.pdfFormat)
self.saveReport(data: pdf)
}
private var pdfFormat: UIGraphicsPDFRendererFormat {
let format = UIGraphicsPDFRendererFormat()
// TODO: Add Author here
let metadata = [kCGPDFContextCreator: "Mike",
kCGPDFContextAuthor: viewModel.userService.userFullName]
format.documentInfo = metadata as Dictionary<String, Any>
return format
}
private func saveReport(data: Data?) {
guard let data = data else {
log.error("No data generated for pdf.")
return
}
do {
try viewModel.saveReport(data)
print("Report generated for inspection")
} catch {
print("Failed to create pdf report due to error: \(error)")
}
// Used to verify report in simulator
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
var filename = "\(UUID().uuidString.prefix(6)).pdf"
let bodyPath = documentDirectory.appendingPathComponent(filename)
do {
let value: Data? = viewModel.savedReport
try value?.write(to: bodyPath)
print("pdf directory: \(documentDirectory)")
} catch {
print("report not generated")
}
}
// MARK: Size Preference Key
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
}
// MARK: Get image from SwiftUI View
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
func toPDF(format: UIGraphicsPDFRendererFormat) -> Data {
let controller = UIHostingController(rootView: self)
let view = controller.view
let contentSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: contentSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsPDFRenderer(bounds: controller.view.bounds, format: format)
return renderer.pdfData { context in
context.beginPage()
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
I landed on using a size preference key because a lot of other options did not perform as expected. For instance, setting the content size in .onAppear ended up yielding a pdf with a lot of space above and below it. At one point I was creating the pdf using a boolean state that when changed would use the geometry reader's current geometry. That generally worked, but would provide unexpected results when the user would rotate the device while a modal was open.
References (as best I can recall):
SizePreferenceKey: https://swiftwithmajid.com/2020/01/15/the-magic-of-view-preferences-in-swiftui/
Snapshot View extension: https://www.hackingwithswift.com/quick-start/swiftui/how-to-convert-a-swiftui-view-to-an-image

Is there any way to force execute the OnSizeAllocated method?

I have created a view using Xaml code behind. I did it using the code behind because I wanted to change the layout of the view based on the device orientation. So, the problem which I am facing is that the OnSizeAllocated method is being called after the view is loaded. So, it is unable to change the layout as per the device orientation. I just want to know if there is any way to invoke the OnSizeAllocated method before the view is loaded. Please click on the below link to view the code:
Please click Here to view the Code
1.Rearrange the Page
you could check if width is greater than height to determine if the device is now in landscape or portrait:
public partial class Page13 : ContentPage
{
private double _width ;
private double _height ;
private Grid grid;
private Label label;
private Entry entry;
private Button button;
public Page13 ()
{
_width = this.Width;
_height = this.Height;
label = new Label(){Text = "i am a laber"};
entry = new Entry(){WidthRequest = 200};
button = new Button(){Text = "Submit"};
grid = new Grid();
UpdateLayout();
StackLayout stackLayout = new StackLayout();
stackLayout.Children.Add(grid);
Content = stackLayout;
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (_width != width || _height != height)
{
_width = width;
_height = height;
UpdateLayout();
}
}
void UpdateLayout()
{
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
grid.Children.Clear();
if (_width > _height)
{
ScreenRotatedToLandscape();
}
else
{
ScreenRotatedToPortrait();
}
}
private void ScreenRotatedToLandscape()
{
grid.RowDefinitions.Add(new RowDefinition(){Height = new GridLength(1,GridUnitType.Auto)});
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1,GridUnitType.Auto)});
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
grid.Children.Add(label,0,0);
grid.Children.Add(entry, 1, 0);
grid.Children.Add(button, 0, 1);
Grid.SetColumnSpan(button,2);
}
private void ScreenRotatedToPortrait()
{
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
grid.Children.Add(label, 0, 0);
grid.Children.Add(entry, 0, 1);
grid.Children.Add(button, 0, 2);
}
}
This is the recommended implementation pulled right from the Xamarin.Forms documentation.
2.Using Xamarin.Essentials
It adds additional functionality to cross-platform applications built in Xamarin. One of these new features is the ability to ping the device for the current orientation by accessing the DeviceDisplay.ScreenMetrics.Orientation property. This returns the current device orientation, which can be used to determine which layout to render.
it's similar to the one above
private bool IsPortrait;
public Page13 ()
{
...
IsPortrait = DeviceDisplay.ScreenMetrics.Orientation == ScreenOrientation.Portrait;
UpdateLayout();
...
}
void UpdateLayout()
{
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
grid.Children.Clear();
if (IsPortrait)
{
ScreenRotatedToPortrait();
}
else
{
ScreenRotatedToLandscape();
}
}
You can't force run that since the SizeAllocation hasn't changed, but you could do this to get orientation on initial load:
If you add the Xamarin.Essentials nuget package, as you can see here, you can get the orientation using this line of code DeviceDisplay.MainDisplayInfo.Orientation and you will get Landscape, Portrait, Square, or Unknown.
If you don't want to add the package, you can just use Application.Current.MainPage.Width and Application.Current.MainPage.Height to figure out orientation.

Sliding in and out using xamarin

I am trying to implement this kind of design as shown in fig on my page, how can I achieve this in Xamarin.forms mainly in shared project. As I am trying to reduce the complexity by doing in platform specific.
I tried using this even https://github.com/XAM-Consulting/SlideOverKit .
But the issue is I can't slide in the menu when its open (i.e after touching on page I want the menu to hide) but it doesn't happen. We need to manually drag that out to close it.
So please let me know how to achieve this.
Thanks
Based on your description, I modifed my code,there is a GIF
When you click the ImageButton, you should call this method this.HideMenu();
code of QuickInnerMenuPage
public QuickInnerMenuPage()
{
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children = {
new Label(){Text="1222"}
}
};
this.SlideMenu = new QuickInnerMenuView(MenuOrientation.RightToLeft);
QuickInnerMenuView.ib.Clicked += (o, e) =>
{
this.HideMenu();
};
}
}
There is code of QuickInnerMenuView
public class QuickInnerMenuView : SlideMenuView
{
public static ImageButton ib;
public QuickInnerMenuView (MenuOrientation orientation)
{
ib = new ImageButton
{
Source = "Happy.png",
WidthRequest = 25,
HeightRequest = 25,
};
var mainLayout = new StackLayout {
Spacing = 15,
Children = {
ib,
new ImageButton {
Source = "Home.png",
WidthRequest = 25,
HeightRequest = 25,
},
new ImageButton {
Source = "MessageFilled.png",
WidthRequest = 25,
HeightRequest = 25,
},
new ImageButton {
Source = "Settings.png",
WidthRequest = 25,
HeightRequest = 25,
},
}
};
// In this case the IsFullScreen must set false
this.IsFullScreen = false;
this.BackgroundViewColor = Color.Transparent;
// You must set BackgroundColor,
// and you cannot put another layout with background color cover the whole View
// otherwise, it cannot be dragged on Android
this.BackgroundColor = Color.FromHex ("#C82630");
this.MenuOrientations = orientation;
if (orientation == MenuOrientation.BottomToTop) {
mainLayout.Orientation = StackOrientation.Vertical;
mainLayout.Children.Insert (0, new Image {
Source = "DoubleUp.png",
WidthRequest = 25,
HeightRequest = 25,
});
mainLayout.Padding = new Thickness (0, 5);
// In this case, you must set both WidthRequest and HeightRequest.
this.WidthRequest = 50;
this.HeightRequest = 200;
// A little bigger then DoubleUp.png image size, used for user drag it.
this.DraggerButtonHeight = 30;
// In this menu direction you must set LeftMargin.
this.LeftMargin = 100;
}
Do you add want to achieve it like following GIF.
If so,when you used SlideMenu, you should Create a new view that inherit's from MenuContainerPage like following code.Note:MenuContainerPage comes from SlideOverKit
public class QuickInnerMenuPage: MenuContainerPage
{
public QuickInnerMenuPage()
{
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children = {
new Label(){Text="1222"}
}
};
this.SlideMenu = new QuickInnerMenuView(MenuOrientation.RightToLeft);
}
}
Here is my demo.you could refer to it.
https://github.com/851265601/SlideOverKitDemo

QML iterating through model

I am trying to draw the content of a ListModel to a canvas in QML. The content of this model is displayed in a ListView elsewhere in the application, so I know the model is correctly filled with content.
No I am trying to update the canvas every time the model data changes:
import QtQuick 2.7
import QtQml.Models 2.2
Item {
Canvas {
anchors.fill: parent
id: canvas
onPaint: {
console.log("onPaint()")
var ctx = getContext("2d")
ctx.fillStyle = Qt.rgba(0, 0, 0, 1)
ctx.fillRect(0, 0, width, height)
console.log(particleListModel.count)
for(var i = 0; i < particleListModel.count; i++) {
console.log(i)
}
}
}
Connections {
target: particleListModel
onDataChanged: {
console.log("data changed")
canvas.requestPaint()
}
}
}
Once I change the data (in C++) I receive the dataChanged() signal and the onPaint() of the canvas gets called. However the debug output of
console.log(particleListModel.count)
is "undefined".
How can this be, while the regular ListView is able to display the content correctly?
When working with the Model you need to call the rowCount function instead of count since the latter is a property of the ListView and not of the model.
The following should work:
console.log(particleListModel.rowCount())

Do you know inheritance in createjs?

Anyone have any experience with inheritance and createjs. I am trying to extend the createjs.Container class with my button class but I am getting the following error in my browser console. Eveything seems to be working fine which makes me think the problem must be when the page loads. I have played around with the order that I load my scripts but it still produces the error. Below is the structure of my class.
TypeError: this.Container_constructor is not a function
(function() {
function Button(bmp, w, h) {
this.Container_constructor();
this.setup();
}
var p = createjs.extend(Button, createjs.Container);
p.setup = function() {
//code here
} ;
window.Button = createjs.promote(Button, "Container");
}());
After further poking around the problem is actually in my StartBtn class that extends my Button class. This is my working version.
(function() {
function StartButton(bmp, w, h) {
this.Container_constructor();
this.bmp = bmp;
this.width = w;
this.height = h;
this.setup();
}
var p = createjs.extend(StartButton, Button);
window.StartButton = createjs.promote(StartButton, "Button");
}());
Then just instantiate the StartButton and add it to the stage.
this.startBtn = new StartButton(loader.getResult('btn_start'), 227, 50);
this.addChild(this.startBtn)