-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is there any way to add "DataLabels" like Highcharts. That is, to provide above/next to the bar/column the value of the y axis? #2
Comments
This is currently not implemented, no. |
If it is a desirable feature, I can look into it and suggest an implementation. If that's the case, could you @johannes-wolf or someone give me some pointers on how to start thinking about this? I'm not familiar with CeTZ the source code yet. |
Sure. The barchart implementation is here: https://github.com/johannes-wolf/cetz/blob/master/src/lib/plot/bar.typ. You can do the drawing in the _stroke function: https://github.com/johannes-wolf/cetz/blob/bf3ec2f6894ccd9255243e1fa40d2b56d6ddcd5b/src/lib/plot/bar.typ#L72 If those numbers are drawn (and where) should be styleable |
Well, actually I think that it makes more sense to draw it here while iterating through each bar, doesn't it? For example: #let _draw-rects(self, ctx, ..args) = {
let x-axis = ctx.x
let y-axis = ctx.y
let w = self.bar-width
for d in self.data {
let (x, n, len, y-min, y-max) = d
let x-offset = _get-x-offset(self.bar-position, self.bar-width)
let left = x - x-offset
let right = left + w
let width = (right - left) / len
if self.mode in ("basic", "clustered") {
left = left + width * n
right = left + width
}
if (left <= x-axis.max and right >= x-axis.min and
y-min <= y-axis.max and y-max >= y-axis.min) {
left = calc.max(left, x-axis.min)
right = calc.min(right, x-axis.max)
y-min = calc.max(y-min, y-axis.min)
y-max = calc.min(y-max, y-axis.max)
---------------------------------------------------------------------------------------------------------------
draw.rect((left, y-min), (right, y-max))
// new attribute data-label
if self.data-label {
// Draw data-label here with an offset relative to the top of the bar
}
---------------------------------------------------------------------------------------------------------------
}
} I couldn't understand how to draw text on that context though. I'm on the right track? If that's the case, can you give me some insight into how to draw text in that "if self.data-label" line? |
You are on the right track. You can just use the normal Note, that scaling is already set-up when this function is called. |
Now I got it. I missed the draw.content function. I arrived on this: if self.data-label != none {
let offset = self.data-label.at("offset")
let size = self.data-label.at("text-size")
draw.content((((left) + right)/2, y-max+offset), text(size:size)[#y-max])
} Then I pass this to the columnchart parameters: data-label:(offset: 0.4, text-size: 7pt) The offset doesn't work in a fixed manner because it scales, so you need to adjust it. It kinda works for me right now. If you wish we can see how to exactly make this follow the API style. Example: |
Looks good. You should use relative coordinates to mix canvas units with absolute units: You should also use add: Also, the |
I tried to follow the draw.content function call that you suggested, but couldn't manage to make it work: let data_label = text(size:size)[#y-max]
draw.content(rel: (0, offset), to: ((left + right) / 2, y-max), anchor:"south", data_label) It gives this error:
I checked the docs on code and pdf but couldn't manage to understand how to call it correctly. The docs don't mention these 'rel' and and 'to' parameters. Am I missing something? |
You are missing parentheses arround your coordinate. rel: and to: are not arguments to content. |
Oh! Right. Now I got it. What I have now is the following: draw.rect((left, y-min), (right, y-max))
if self.style.data-label != none {
let offset = self.data-label.at("offset")
let size = self.data-label.at("text-size")
let data_label = text(size:size)[#y-max]
if y-axis.horizontal {
draw.content((rel: (offset, 0), to: (right, (y-min + y-max) / 2)), anchor:"west", data_label)
} else {
draw.content((rel: (0, offset), to: ((left + right) / 2, y-max)), anchor:"south", data_label)
}
} It is working for columnchart but not for barchart. I tried to mess up with parameters but the data-labels didn't appeared at all in the barchart case. Don't know what I'm missing. Also I couldn't understand how to propagate the 'data-label' from:
To the _draw-rects(self, ctx) function above where I need to use the data-label from style dict. Can you give more pointers how to continue? |
Nevermind! I got it. Found on the source code that you needed to use 'draw.group(ctx => {})'. draw.rect((left, y-min), (right, y-max))
draw.group(ctx => {
if ctx.style.data-label != none {
let offset = ctx.style.data-label.at("offset")
let size = ctx.style.data-label.at("text-size")
let data_label = text(size:size)[#y-max]
if y-axis.horizontal {
draw.content((rel: (offset, 0), to: (right, (y-min + y-max) / 2)), anchor:"west", data_label)
} else {
draw.content((rel: (0, offset), to: ((left + right) / 2, y-max)), anchor:"south", data_label)
}
}
}) I just need to know why the data-labels aren't appearing on the barchart. |
Done! bar.typ: draw.group(ctx => {
if ctx.style.data-label != none {
let offset = ctx.style.data-label.at("offset")
let size = ctx.style.data-label.at("text-size")
let data_label = text(size:size)[#y-max]
let anchor = if y-axis.horizontal {"west"} else {"south"}
draw.content((rel: (0, offset), to: ((left + right) / 2, y-max)), anchor:"south", data_label)
}
}) I needed to make some changes to columchart.typ and barchart.typ to include these: #let columnchart-default-style = (
axes: (tick: (length: 0), grid: (stroke: (dash: "dotted"))),
bar-width: .9,
x-inset: 0.6,
data-label: (offset: 0.20, text-size: 8pt)
) barchart.typ #let barchart-default-style = (
axes: (tick: (length: 0), grid: (stroke: (dash: "dotted"))),
bar-width: .9,
y-inset: 1,
data-label: (offset: 0.10, text-size: 8pt)
) The only problem that persists that I've noticed is that the style values aren't overriding the defaults, that applies not only for data-label, but for other custom values like "bar-width" and "x-inset". I mean: canvas({
draw.set-style(
x-inset: 0,
legend: (fill: white),
padding: 1.5pt,
data-label: data-label,
)
chart.columnchart(
data,
label-key: 0,
value-key: (..range(1, labels.len() + 1)),
mode: "clustered",
size: size,
y-label: smallcaps[Escala de Likert],
x-label: smallcaps[Perguntas],
y-tick-step: 0.5,
y-min: 1,
y-max: 5,
labels: labels,
legend: "legend.north-east",
bar-style: p
)
} )
} set-style isn't working for 'x-inset', 'barwidth' and 'data-label'. I suspect the merge of the set-style dict and the bar defaults isn't working properly. Maybe it is a bug? Current state of the charts: |
Nice! You must not use Do you want to open up a PR with your changes? I can also add some additions myself. If we add this feature I would like to have it more configurable: overriding the content anchor, specifying the side of the bar that is used as anchor etc. |
Ok! I've created the PR, take a look! I looked into using the styles.resolve but couldn't see exactly how to use it. I would need to check which default-style to use inside the draw-rects. Also the 'add-bar' callback you mentioned I didn't understand how to implement it exactly. If you need anything just comment on the PR and I can try to help. |
Is this planned to be merged into cetz-plot soon? I don't see a PR for it in this project. |
The cetz-plot repo moved, therefore this PR is in the wrong repo. |
Original PR is here: cetz-package/cetz#516 |
It is not easy right now to implement this in a good way, because of how plot scaling works, see #4. |
Would it be possible to have a |
Yes. I'll try to add something after refactoring the plot environment to make sub-plots possible. |
I mean something like this:
The text was updated successfully, but these errors were encountered: