Skip to content
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

How to use SVG/images as axis ticks? #513

Open
fromaline opened this issue Dec 20, 2024 · 6 comments
Open

How to use SVG/images as axis ticks? #513

fromaline opened this issue Dec 20, 2024 · 6 comments

Comments

@fromaline
Copy link

Hey, thanks for this fantastic library!

I’m using it in a Svelte/SvelteKit project and’d like to use SVG as an axis tick label. I tried using tickFormat to return an <img> tag or an SVG directly, but it doesn't work. Is there a way to do it?

@rokotyan
Copy link
Contributor

Thanks @fromaline

Axis tick labels don't support custom content. This would be a great addition to the library. Let me know if you want to try adding this feature yourself, I'll be happy to point you to the right place in the code.

@fromaline
Copy link
Author

@rokotyan, thanks for your response!

I really need this functionality for my project, so I’ll give it a shot. Could you please point me in the right direction?

@rokotyan
Copy link
Contributor

@fromaline Once you've cloned the repo, do npm install and then npm run dev. This will run our demo app.

Search for an Axis example there. Ideally copy an example to have a dedicated one for the feature you'll be working on (maybe copy this one packages/dev/src/examples/xy-components/axis/single-axis).

Then explore this section of the Area component's code:

tickText.each((value: number | Date, i: number, elements: ArrayLike<SVGTextElement>) => {
let text = config.tickFormat?.(value, i, tickValues) ?? `${value}`
const textElement = elements[i] as SVGTextElement
const textMaxWidth = config.tickTextWidth || (config.type === AxisType.X ? this._containerWidth / (ticks.size() + 1) : this._containerWidth / 5)
const styleDeclaration = getComputedStyle(textElement)
const fontSize = Number.parseFloat(styleDeclaration.fontSize)
const fontFamily = styleDeclaration.fontFamily
const textOptions: UnovisTextOptions = {
verticalAlign: config.type === AxisType.X ? VerticalAlign.Top : VerticalAlign.Middle,
width: textMaxWidth,
textRotationAngle: config.tickTextAngle,
separator: config.tickTextSeparator,
wordBreak: config.tickTextForceWordBreak,
}
if (config.tickTextFitMode === FitMode.Trim) {
const textElementSelection = select<SVGTextElement, string>(textElement).text(text)
trimSVGText(textElementSelection, textMaxWidth, config.tickTextTrimType as TrimMode, true, fontSize, 0.58)
text = select<SVGTextElement, string>(textElement).text()
}
const textBlock: UnovisText = { text, fontFamily, fontSize }
renderTextToSvgTextElement(textElement, textBlock, textOptions)
})

Most likely your changes will go somewhere there.

@fromaline
Copy link
Author

@rokotyan, thanks for the info!

I went through the code, and it seems highly tailored toward text-based ticks. Do you have any suggestions for implementing images as ticks? I’d like to keep it as simple as possible, but it feels like it might require quite a bit of coding and testing — I’m not sure it’s worth the effort.

@rokotyan
Copy link
Contributor

@fromaline Yes, you'll need to completely replace the <text> element of each tick.

If I were to implement this, I would allow tickFormat to return a custom SVG string, then check in the code to see if the current tick value has any SVG markup. And if it does, completely replace the contents of g.tick with that custom markup.

The Graph component has something similar:

selection.each((d, i, elements) => {
const element = select(elements[i])
const shape = getString(d, shapeAccessor, index) as GraphNodeShape
let shapeElement: Selection<SVGPathElement, unknown, null, undefined>
| Selection<SVGRectElement, unknown, null, undefined>
| Selection<SVGGElement, unknown, null, undefined>
| Selection<SVGCircleElement, unknown, null, undefined>
const isCustomShape = isStringSvg(shape)
if (isCustomShape) {
shapeElement = element.insert('g', insertSelector)
.html(sanitizeSvgString(shape))
} else {

@fromaline
Copy link
Author

@rokotyan, got it! Thanks for the hint!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants