My layout consists of 2 texts with an arrow image between them. All elements are placed in a composable Row and aligned to the left, right behind eachother. When the texts are short it looks like this:
|Origin → destination |
When the texts are too long, I'd like them to ellipsise with max 1 line. If the 'destination' is too long, it works as expected:
|Origin → looooooooongDest...|
But what if the 'origin' text is too long?
|LooooooooooooooongOrigin → .| <<< wrong!
In xml ConstraintLayout I'd use a layout_constraintWidth_percent=0.5 for the first text, which results in the following:
|Loooooooo... → destination |
Simply put, this is my request:
Texts and icons are all aligned left, after eachother
Texts are always 1 line max and ellipsized if too long
The left text is max 50% of the total row width
The right text can fill the rest of the width
How can I achieve this in compose?
Note: I've tried using combinations of .weight(0.5F) and .weight(0.5F, fill = false) on either the first or both texts. These are some of the results I got with these solutions, which is not what want either, but I think I'm getting closer to the desired result with these modifiers:
|Origin → destination | <<< wrong!
|Or.. → looooooongDestination| <<< wrong!
My latest code is getting close to the desired solution:
Row {
Text(
modifier = Modifier.weight(0.5F, fill = false),
text = "Origin",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Icon(
modifier = Modifier.padding(horizontal = 4.dp),
...
)
Text(
modifier = Modifier.weight(0.5F),
text = "Destination",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Although this seems to work as expected for the 1st text, it always truncates the 2nd text to 50% width:
|Origin → loooongDest... | <<< wrong!
If you know ConstraintLayout you can implement directly in ConstraintLayout for compose.
Here is approximately what you described in ConstraintLayout Compose:
It will clip the first text when it grows past 50%
It will use the rest and clip if it fills the rest of the ConstraintLayout container.
It has 2 syntaxes. JSON is a little easier to read the constraints as it is separated. The DSL is a little more compact and more in the style of the rest of Compose.
Here are the examples in both formats:
JSON
#OptIn(ExperimentalMotionApi::class)
#Preview
#Composable
fun DemoCLText() {
val c = ConstraintSet("""{
midpoint: { type: 'vGuideline', percent: 0.50 },
origin: {
width: { value: 'preferWrap', max: 'wrap' },
start: ['parent', 'start', 0],
end: ['midpoint', 'start', 0],
centerVertically: 'parent',
hBias:0
},
arrow: {
width: 'wrap',
centerVertically: 'parent',
start: ['origin', 'end', 0],
},
destination: {
width: { value: 'preferWrap', max: 'wrap' },
start: ['arrow', 'end', 0],
end: ['parent', 'end', 2],
hBias: 0,
centerVertically: 'parent'
},
}
"""
);
ConstraintLayout(
constraintSet = c,
modifier = Modifier.width(200.dp).background(Color.LightGray)
) {
Text(
modifier = Modifier.layoutId("origin"),
text = "Origin",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
modifier = Modifier.layoutId("arrow"),
text = "→",
)
Text(
modifier = Modifier.layoutId("destination"),
text = "Destination",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
DSL
#OptIn(ExperimentalMotionApi::class)
#Preview
#Composable
fun DemoCLText2() {
ConstraintLayout(
modifier = Modifier.width(140.dp).background(Color.LightGray)
) {
val origin = createRef()
val midpoint = createGuidelineFromAbsoluteRight(0.5f)
Text(
modifier = Modifier.constrainAs(origin) {
centerVerticallyTo(parent)
start.linkTo(parent.start)
end.linkTo(midpoint)
width = Dimension.preferredWrapContent.atMostWrapContent
horizontalBias = 0f
},
text = "Origin",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
var arrow = createRef();
Text(
modifier = Modifier.constrainAs(arrow) {
centerVerticallyTo(parent)
start.linkTo(origin.end)
width = Dimension.wrapContent
},
text = "→",
)
Text(
modifier = Modifier.constrainAs(createRef()) {
centerVerticallyTo(parent)
start.linkTo(arrow.end)
end.linkTo(parent.end)
width = Dimension.preferredWrapContent.atMostWrapContent
horizontalBias = 0f
},
text = "Destination",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Related
I would love to have your input how to solve this issues.
I have a time series (x=date) ggplot with bars (y=cmass1), and my bars are on position="stack" using a factor (polymer) to display polymer types for each sample.
Each bar represents another sampling station (factor sample), and I would like to identify which bar is which station (factor "sample" in my data frame) and add the label above each bar.
I've come so far that I can show the "sample" labels, but then there appear multiple replicated labels (one for each data point in the stacked parts of the bar). But I need only one per bar!
Can someone help, how I can create one label per bar (above each bar) that identifies which sample it is (without manual annotation)?
Additional date display problem:
Also, on one date (11 November) I have two samples, and in this plot they are cumulated, but I would need them next to each - so two as stacked bars on one date (like with besides=TRUE, but that doesn't seem to work with stacked bars). Any help here?
I hope it is clear, and thanks so much for any help!
Cleo
The plot, where S1, S2 etc should be above each bar as a label
Dataframe view_partial
ggplot(Merg1[!(is.na(Merg1$campaign)),] ,
aes(date,y=cmass1,group_by(sample1), fill=polymer))+
geom_col(show.legend = T, alpha = 0.8, position="stack",
stat="identity")+
xlab("July 2021 November 2021 February 2022 July 2022") +
facet_grid(.~campaign, scales = "free_x",space = "free_x" )+
geom_point(aes(y=montsouris_mm*20 ),color="#A3009F",size=2.5, position="dodge")+
geom_point(aes(y=melun_mm*20 ),color="#558C8C",size=2.5, position="dodge")+
geom_point(aes(y=fauville_mm*20 ),color="#E8DB7D",size=2.5, position="dodge")+
scale_y_continuous(expand = c(0,0),limits=c(0, 1000), breaks = seq(0, 1000, by = 100),
sec.axis = sec_axis(~./20, name = "Precipitation (mm)",
breaks = seq(0, 50, by = 10))) +
scale_x_date(date_breaks = "1 day", labels = date_format("%d"))+
ylab(expression(bold(paste(~C[mass]~ (µg ~L^{-1}))))) +
theme(axis.text.x=element_text(color="black", size=12, angle=0, vjust=0.5),
axis.text.y=element_text(color="black", size=12, angle=0, vjust=0.5),
axis.ticks.x=element_blank(),
axis.title.x = element_text(size = 12,
color = "black",
face = "bold"),
strip.background = element_rect(color="white", fill="white", size=0.5,
linetype="solid"),
legend.text = element_text(color = "black",size = 12),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_rect(size = 0.5,colour="#D4CDCB",
fill="white" ,linetype = 'solid'),
legend.key.height= unit(0.2, 'cm'),
legend.key.width= unit(0.4, 'cm'))+
scale_fill_manual(name = "", values = MPcol, breaks = c("abs","acrylic", "acrylic paints", "alkyd","epoxy", "pa", "pan_acrylic fibre", "pe", "polyester", "pp", "ps", "pu", "pvac", "pvc", "sbr", "vinyl copolymer"),
labels=c("Acrylonitrile butadiene styrene (ABS)","Acrylic", "Acrylic paint", "Alkyd", "Epoxy", "Polyamide (PA)", "Pan acrylic fibre", "Polyethylene (PE)", "Polyester (PET)", "Polypropylene (PP)", "Polystyrene (PS)", "Polyurethane (PU)", "Polyvinyl acetate (PVAC)", "Poly vinyl chloride (PVC)", "Styrene butadiene rubber (SBR)", "Vinyl copolymere"))+
theme(legend.position = "bottom",
legend.background = element_rect(fill = "#FFFCFB", # Background
colour = 1),
legend.title = element_text(family = "sans",
color = "black",
size = 10,
face = 2)) +
theme(panel.grid.minor = element_line(color = "#D4CDCB",
size = 0.15,
linetype = 2))+
theme(strip.text.x = element_text(size=14, face="bold",
vjust = 2, color="black")) +
#geom_text( label = label1, vjust = -1, position = position_dodge(0.90), size = 3, hjust=-0.5)+
labs(title = "Microplastics",
subtitle = "Mass Concentrations by Polymer Types",
caption = "Still missing S4 repeated samples and M1 in Campaign D. S4 in Campaign C cumulated repetaed samples. MPs >300µm are excluded, also natural particles are excluded.",
tag = "Fig. 5-1") +
theme(plot.title = element_text(family = "serif", # Font family
face = "bold", # Font face
color = 1, # Font color
size = 16, # Font size
hjust = 1, # Horizontal adjustment
vjust = 1, # Vertical adjustment
angle = 0, # Font angle
lineheight = 1, # Line spacing
margin = margin(5, 0, 0, 0)), # Margins (t, r, b, l)
plot.subtitle = element_text(family = "serif",
hjust = 1, size=14), # Subtitle customization
plot.caption = element_text(hjust = 0.25, size =11,
family = "serif",face = "italic"), # Caption customization
plot.tag = element_text(face = "italic"), # Tag customization
plot.title.position = "plot", # Title and subtitle position ("plot" or "panel")
plot.caption.position = "panel", # Caption position ("plot" or "panel")
plot.tag.position = "top") # Tag position
1. Background:
I use konvajs to create a "table" component:
structure:
Stage
|
Layer
|
+-----+--------------+
| |
Group(tableGroup) Group(tableGroup)
|
+--------------+--------+
| |
Group(cellGroup) Group(cellGroup)
|
+----+----+
| |
Shape(Rect) Shape(Text)
image:
table component
2. Target:
I want every text shape to be width adaptive.
I found it in the official documents of konvajs:
How to change width of the text with transforming tool? https://konvajs.org/docs/select_and_transform/Resize_Text.html
Scenario 1: add all "cellGroup" to "transformer"
// My code
tableGroup.on('click', function (e) { // Click the selected table
tr.nodes([])
tableGroup.getChildren().map(function(item) {
tr.nodes(tr.nodes().concat(item)); // Add all cellGroups to transformer
item.off('transform')
item.on('transform', (e) => {
item.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
item.getChildren().map(function(child){
child.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
})
})
})
});
Scheme 2: add "tablegroup" to "transformer"
// My code
tableGroup.on('click', function (e) { // Click the selected table
tr.nodes([tableGroup]) // Add all tableGroup to transformer
tableGroup.on('transform', (e) => {
tableGroup.getChildren().map(function(item) {
item.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
item.getChildren().map(function(child){
child.setAttrs({
width: item.width() * item.scaleX(),
height: item.height() * item.scaleY(),
scaleX: 1,
scaleY: 1,
});
})
})
})
});
conclusion: Scheme 1 is feasible, but scheme 2 is not.My requirement is to add "tableGroup" to transformer and realize text width adaptation.Find a solution, thank you very much.
3. Other:
Q: Why must "tableGroup" be added to "transformer"?
A: Because when moving a "Group" or "Shape" with "Transformer", the coordinates (x, y) of the "Group" or "Shape" will be changed. I don't want to change the coordinates of "cellGroup", I want to change the coordinates of "tableGroup" (x, y). Or you have a better solution.Find a solution, thank you very much.
Here is a solution using two groups - one for the table outline and a second to contain the cells. The cell group has its attrs set to follow the table group as it is transformed - excluding the scale!
This is not a perfect solution that you can cut & paste as a usable component but should show you a potential alternative.
/*
* From here onwards we set up the stage and its contents.
*/
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
layer = new Konva.Layer(),
tblGroup = new Konva.Group({draggable: true}),
cellGroup = new Konva.Group(),
cellRect = new Konva.Rect({strokeWidth: 1, stroke: 'black', name: 'cellRect'}),
cellText = new Konva.Text({fontName: "Arial", fontSize: 20, fill: 'black', name: 'cellText'}),
tr = new Konva.Transformer();
stage.add(layer);
layer.add(tr);
// Using this plain JS objet to define the table and relative cell positions.
const tblData = {
position: { x: 100, y: 100, width: 202, height: 82},
cells: [
{x: 1, y: 1, width: 100, height: 40, text: 'Cell 1-1'},
{x: 101, y: 1, width: 100, height: 40, text: 'Cell 1-2'},
{x: 1, y: 41, width: 100, height: 40, text: 'Cell 2-1'},
{x: 101, y: 41, width: 100, height: 40, text: 'Cell 2-2'},
]
}
const tableGroup = tblGroup.clone({x: tblData.position.x, y: tblData.position.y}),
tblPosGroup = tableGroup.clone(), // position exactly as tableGroup.
tblRect = cellRect.clone({x:0, y: 0, width: tblData.position.width, height: tblData.position.height});
tblRect.stroke('red');
tableGroup.add(tblRect);
// add the cells.
for (let i = 0; i < tblData.cells.length; i++){
const
cell = cellGroup.clone({}),
rect = cellRect.clone(tblData.cells[i]),
text = cellText.clone(tblData.cells[i]);
// Note we stach the positioning data into the Konva shape instances in a custom attr - used in the transform event
rect.setAttrs({posData: tblData.cells[i]});
text.setAttrs({posData: tblData.cells[i]});
cell.add(rect,text);
tblPosGroup.add(cell);
}
layer.add(tableGroup, tblPosGroup);
tableGroup.on('transform', (e) => {
// make the cells group follow the tbl group as it is transformed
tblPosGroup.setAttrs({position: tableGroup.position(), rotation: tableGroup.rotation()});
// find all the objects we want to manager - using the shape name() attr which we search with a dot prefix.
tblPosGroup.find('.cellRect, .cellText').map(function(item) {
const cellPos = item.getAttr("posData");
// set the position and size of the cells referring to original position & size data and applying scale from transformer.
item.setAttrs({
x: cellPos.x * tableGroup.scaleX(),
y: cellPos.y * tableGroup.scaleY(),
width: cellPos.width * tableGroup.scaleX(),
height: cellPos.height * tableGroup.scaleY()
});
})
})
tblPosGroup.on('click', function (e) { // When the table is clicked - actually we listen on the cells group as this will get the click
tr.nodes([tableGroup]); // Add all tableGroup to transformer
e.cancelBubble = true;
})
stage.on('click', function (e) {
tr.nodes([]);
})
body {
margin: 20px;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
<p>A table-like construction. The aim was to keep cell contents unchanged in size and scale whilst allowing a transformer to be used.</p>
<p>Solution is two groups. Group#1 is the main table rect and group#2 is the cells. Group#2 attrs are set to follow group#1 attrs in the group#1.onTransform event listener.</p>
<div id="container"></div>
I need to present a table transposed from the usual manner data is presented (i.e. in the table required, each record is represented by a column, and each field is represented by a row).
I would like each column aside from the 1st (header column) to have the same width.
Overflowing columns will be in a new table sitting below the table above (see below).
For the final table (which will usually have a different number of columns to the table sitting above it), the width of the columns should be the same.
Therefore in the .PDF generated by the snippet below, the 2 tables should align as so (noting the exact number of cells will differ):
+-----+----+----+----+----+----+----+----+----+----+----+
|Day | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
+-----+----+----+----+----+----+----+----+----+----+----+
+-----+----+----+----+----+
|Day | 11 | 12 | 13 | 14 |
+-----+----+----+----+----+
// generating the data for the table
const cols = 14;
const headers = (new Array(cols + 1)).fill(0).map((x,i) => (i).toString());
headers[0] = 'DAY #:';
const today = new Date();
const data = [headers.map((x, i) => {
const date = new Date(today);
date.setDate(date.getDate() + i);
return date.toLocaleDateString(navigator.languages);
}),
headers.map((x, i) => (1 - i / cols).toPrecision(3)),
];
data[0][0] = "Date:"
data[1][0] = "Dose:"
// splitting the table data at 10 columns + rowHeader wide
const maxCols = 10;
const tables = [];
const tableCount = Math.ceil(cols/maxCols)
for (let i = 0; i < tableCount; ++i) {
const slicePos = 1 + i * maxCols;
const mapColsInRange = (d) => [d[0]].concat(d.slice(slicePos, slicePos + maxCols));
tables.push({
headers: mapColsInRange(headers),
data: data.map(mapColsInRange),
});
}
// creating the tables
const doc = new jsPDF('l', 'mm', 'a4');
for (const t of tables) {
doc.autoTable({
theme: 'grid',
head: [ t.headers ],
body: t.data,
pageBreak: 'avoid',
styles: { halign: 'center', cellWidth: 21 },
columnStyles: { 0: { halign: 'left', fontStyle: 'bold', cellWidth: 34 }},
});
}
doc.save('test.pdf');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.1.4/jspdf.plugin.autotable.min.js"></script>
How can the columns be aligned using jsPDF-autoTable options?
adding the autotable option tableWidth: 'wrap' was the solution
const printableColumns = ['Account ID', 'First name', 'Last name', 'Created'];
If you want to change the width of 'Last name' than put the column index number instead of dataKey in 'columnStyles' and set the cellWidth
doc.autoTable({
head: [printableColumns],
body: pdfData, ,
columnStyles: {
2: {cellWidth: 30},
}
});
You can set the columns to be fixed width using cellWidth option.
Defined an own table layouts using pdfmake.js. On print I want per page to contain 7 rows(fixed).I have tried adjusting the height and width of the table cell to contain 7 rows but however if the data in table cell increases the page accumulates with less/more no.of rows.
//layout of the table:
var tablelist={
style: 'tableExample',
table: {
dontBreakRows: true,
widths: [ 20,55,55,55,55,55,55,55,55,55,55,55,55],
headerRows: 1,
body: body
},
layout: {
hLineWidth: function (i, node) {
return (i === 0 || i === node.table.body.length) ? 1 : 1;
},
vLineWidth: function (i, node) {
return (i === 0 || i === node.table.widths.length) ? 1: 1;
},
hLineColor: function (i, node) {
return (i === 0 || i === node.table.body.length) ? 'gray' : 'gray';
},
vLineColor: function (i, node) {
return (i === 0 || i === node.table.widths.length) ? 'gray' : 'gray';
},
},
}
return tablelist;
}
//pushing the table header and other data to the table body
$scope.makePrintTable = function(){
var headers = {
col_1:{ text: 'Day', style: 'tableHeader',rowSpan: 1, alignment: 'center',margin: [0, 8, 0, 0] },
col_2:{ text: 'Date', style: 'tableHeader',rowSpan: 1, alignment: 'center',margin: [0, 8, 0, 0] },
col_3:{ text: '0600-0800', style: 'tableHeader',rowSpan: 1, alignment: 'center',margin: [0, 8, 0, 0] },
.
.
.//Similarly till col_13
col_13:{ text: '1700-1800', style: 'tableHeader',rowSpan: 1, alignment: 'center' ,margin: [0, 8, 0, 0]},
}
body = [];
var row = new Array();
for (var key in headers) {
row.push( headers[key] );
}
body.push(row);
for ( var j=0 ; j< $scope.table.length; j++){
var tableEach={ };
tableEach= $scope.table[j];
/*This for Genarating Object variables*/
for (var i = 1; i < 3; i++) {
window["obj"+i] = new Object();
}
var row = new Array();
var slNoValue = tableEach.slNo;
/*This is for slNo */
obj1["text"] = slNoValue;
obj1["style"]= "cellswidth";
row.push(obj1);
/*This is for Date */
var dateValue = new Date(tableEach.date);
obj2["text"]= dateValue.getDate() + '-' + basicFormats.getMonthName(dateValue.getMonth() )+ '-' + dateValue.getFullYear()+','+ basicFormats.getDayName(dateValue.getDay());
obj2["style"]= "cellswidth";
row.push(obj2);
/*This is for remaining columns (i ranges from 6 to 17 (time in 24hrs clock format) ) */
for(var i=6 ; i<=17 ; i++){
var obj={};
var hourValue = "hour_"+i+"_"+(i+1)+"_value";
var hourValueColor = "hour_"+i+"_"+(i+1)+"_"+"color_value";
hourValue = ( tableEach["hour_"+i] == undefined ? '':(tableEach["hour_"+i]));
hourValueColor =(tableEach["hour_"+i+"_colour"] == undefined ? '#ffffff':(tableEach["hour_"+i+"_colour"]));
obj["text"] = hourValue;
obj["fillColor"] = hourValueColor;
obj["style"] = "cellswidth";
row.push(obj);
console.log(obj);
}
// if( j!= 0 && j % 7 == 0){
// pageBreak : 'before'
// }
}
body.push(row);
}
};
//CSS for tablecells
cellswidth : {
fontSize: 10,
// color:'gray',
bold: true,
alignment: 'center',
margin: [0, 12.55, 0, 12.75],
},
You can use pageBreak function for it:
pageBreakBefore: function(currentNode, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage) {
//Here you can change the criteria according to your requirements
if (currentNode.index % 7 === 0)) {
return true;
}
return false;
},
I am getting as a result a badly rendered pie chart. Why is this happening? Am I doing smth wrong? What can I do to have a better border? I am corrently using v3.5.17.
var w = 500,
h = 500;
//var data = [10, 80, 50, 60, 30, 42, 27, 77];
var max = d3.max(data);
var min = d3.min(data);
var color = d3.scale.ordinal().range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
console.log(color(50));
var canvas = d3.select("#piegraphic").append("svg")
.attr("width", w)
.attr("height", h);
var group = canvas.append("g")
.attr("transform", "translate(200, 200)");
var r = 150;
var arc = d3.svg.arc()
.outerRadius(r - 10)
.innerRadius(0);
var arc2 = d3.svg.arc()
.outerRadius(r + 10)
.innerRadius(0);
var pie = d3.layout.pie()
.value(function (d) {
return d.count;
});
var arcs = group.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc");
var asdf = arcs.append("path")
.attr("d", arc)
.attr("fill", function (d) {
return color(d.data);
})
asdf.on("mouseover", function (d) {
d3.select(this).transition().duration(200).attr("d", arc2);
});
asdf.on("mouseout", function (d, i) {
d3.select(this).transition().duration(200).attr("d", arc);
});
var circle = group.append("circle")
.attr({
"cx": 0,
"cy": 0,
"r": 140,
"fill": "none",
"stroke": "#fff",
"stroke-width": 2
});
I tried other approaches but I always getting the same result I dont know what else I can try. Thanks in advance
EDIT
My data looks like this :
[{count:106136313.3, label : "RR" },{count:136313.3, label : "RA" },{count:1061313.3, label : "TE" }]