Skip to content
Snippets Groups Projects
Commit 2dd27243 authored by Remi  PLANEL's avatar Remi PLANEL
Browse files

add composable to compute rotation text label

parent 45dca804
No related branches found
No related tags found
No related merge requests found
Pipeline #129854 waiting for manual action with stages
in 8 minutes and 26 seconds
......@@ -4,6 +4,7 @@ import * as d3 from "d3";
import type { PlotMargin } from "../types/plot";
import type { StructureGeneLinks, StructureOperonGene, StructureOperonGeneWithCoordinate, StructureOperonGeneWithImg } from "../types/structure"
import { useStructuresBasket } from '~/stores/structuresBasket';
import { useTextRotate, type TextRotateOuput } from '~/composables/useTextRotate';
interface Props {
......@@ -17,29 +18,25 @@ const props = withDefaults(defineProps<Props>(), {
const { genes: genesProps } = toRefs(props)
// const refGenes = ref()
const height = ref<number>(200)
// const height = ref<number>(200)
const svgRef = ref<SVGElement | null>(null)
const structureHeight = ref(130)
const geneHeight = ref(40)
const linkHeight = ref(20)
const margin = ref<PlotMargin>({
marginTop: 5,
marginRight: 7,
marginBottom: 50,
marginBottom: 5,
marginLeft: 7,
})
const gbContainer = ref(null)
const geneToHighlight = ref<string | null>(null)
const snackbar = ref(false)
const color = d3.scaleOrdinal(d3.schemeCategory10);
const plotHeight = computed(() => {
const { marginTop, marginBottom } = toValue(margin)
return toValue(height) + marginTop + marginBottom
})
const domain = computed(() => {
const genes = toValue(computedGenes)
return genes?.map(d => { return d.gene })
})
const innerPadding = ref<number>(8)
const totalGeneLength = computed(() => {
......@@ -66,12 +63,6 @@ const xScaleGenes = computed(() => {
.domain(toValue(domainGenes))
.range([0, computedPlotWidth.value])
})
const yScale = ref(d3.scaleBand()
.paddingInner(0.5)
.domain(['img', 'buff', 'buff2', 'gene', 'label'])
.range([toValue(margin).marginTop, toValue(height)]));
const gbContainer = ref(null)
const computedContainerWidth = computed(() => {
return useElementSize(gbContainer, { width: 500, height: 0 }, { box: 'border-box' }).width.value
})
......@@ -80,13 +71,11 @@ const computedPlotWidth = computed(() => {
return computedContainerWidth.value - marginLeft - marginRight
})
const computedGenes = computed<StructureOperonGene[]>(() => {
const genes = toValue(genesProps)
if (genes !== null && genes?.length > 0) {
let currentSumSize = 0
return genes.map(d => {
return genes.map((d) => {
const size = d?.size ?? 10
const position = currentSumSize
currentSumSize = position + size + innerPadding.value
......@@ -100,9 +89,8 @@ const computedGenes = computed<StructureOperonGene[]>(() => {
}
else { return [] }
})
const structureVersion = computed(() => {
const genesVal = toValue(computedGenes)
if (genesVal?.length > 0) {
......@@ -113,57 +101,101 @@ const structureVersion = computed(() => {
}
})
const structureNodes = computed<StructureOperonGeneWithCoordinate[]>(() => {
const geneNodes = computed<StructureOperonGeneWithCoordinate[]>(() => {
const genes = toValue(computedGenes)
const xScaleVal = toValue(xScale)
const yScaleVal = toValue(yScale)
const xScaleVal = toValue(xScaleGenes)
// const yScaleVal = toValue(yScale)
if (genes !== null) {
return genes.map(d => {
const r = d3.min([toValue(xScale).bandwidth(), toValue(yScale).step() * 3])
// const geneWidth = xScaleVal(d.size)
// const position = xScaleVal(d.position)
// const geneCenter = xScaleVal(d.position) + geneWidth / 2
// const xPostion = geneCenter - r / 2
const x = xScaleVal(d.gene)
const y = yScaleVal('img')
const x = xScaleVal(d.position)
// const y = yScaleVal('gene')
const width = xScaleVal(d.size)
// let rotate = 0
// let labelWidth = (d.gene.length * textSizeRatio.value) / Math.sqrt(2)
// let labelHeight = labelWidth
// if (width < labelWidth) {
// // need to increase angle
// rotate = 90
// labelHeight = labelWidth
// labelWidth = 16
// }
return {
...d,
width,
x,
// rotate: 0,
// labelWidth,
labelHeight: 150,
y: 0,
// y: y === undefined ? 0 : y,
height: 0
// height: yScaleVal.bandwidth()
}
})
}
else { return [] }
})
const yScale = computed(() => {
const { marginTop } = toValue(margin)
const structureHeightVal = toValue(structureHeight) + marginTop
const linkHeightVal = toValue(linkHeight)
const geneHeightVal = toValue(geneHeight)
return {
img: 0,
gene: structureHeightVal + linkHeightVal,
label: structureHeightVal + linkHeightVal + geneHeightVal + 10
}
})
const geneNodesWithY = computed(() => {
const genes = toValue(geneNodes)
const yScaleVal = toValue(yScale)
if (genes !== null) {
const y = yScaleVal.gene
return genes.map(d => {
return {
...d,
r: r,
// x: xPostion > 0 ? xPostion : 0,
x: x === undefined ? 0 : x,
y: y === undefined ? 0 : y,
width: toValue(xScale).bandwidth(),
// width: r,
height: yScaleVal.step() * 3
height: geneHeight.value
}
})
}
else { return [] }
})
const geneNodes = computed<StructureOperonGeneWithCoordinate[]>(() => {
const genes = toValue(computedGenes)
const xScaleVal = toValue(xScaleGenes)
const structureNodes = computed<StructureOperonGeneWithCoordinate[]>(() => {
const genes = toValue(geneNodesWithY)
const xScaleVal = toValue(xScale)
const yScaleVal = toValue(yScale)
if (genes !== null) {
return genes.map(d => {
const x = xScaleVal(d.position)
const y = yScaleVal('gene')
const x = xScaleVal(d.gene)
const y = yScaleVal.img
return {
...d,
width: xScaleVal(d.size),
x,
x: x === undefined ? 0 : x,
y: y === undefined ? 0 : y,
height: yScaleVal.bandwidth()
width: toValue(xScale).bandwidth(),
height: structureHeight.value
}
})
}
else { return [] }
})
const geneLabelHeight = computed(() => {
const heights = toValue(genesTextRotate)
const max = d3.max(Object.values(heights).map(({ height }) => toValue(height)))
return max !== undefined ? max : 0
})
const plotHeight = computed(() => {
const { marginTop, marginBottom } = toValue(margin)
return marginTop + marginBottom + toValue(geneLabelHeight) + toValue(structureHeight) + toValue(geneHeight) + toValue(linkHeight) + 10
})
const linksGenesStruct = computed<StructureGeneLinks[]>(() => {
const geneNodesVal = toValue(geneNodes)
const geneNodesVal = toValue(geneNodesWithY)
const structureNodesVal = toValue(structureNodes)
return d3.zip(geneNodesVal, structureNodesVal).map(([source, target]) => {
......@@ -176,26 +208,30 @@ const linksGenesStruct = computed<StructureGeneLinks[]>(() => {
})
const genesLabel = computed(() => {
const geneNodesVal = toValue(geneNodes)
const geneNodesVal = toValue(geneNodesWithY)
const yScaleVal = toValue(yScale)
return geneNodesVal.map(d => ({ ...d, y: yScaleVal("label") }))
return geneNodesVal.map(d => ({ ...d, y: yScaleVal.label }))
})
onMounted(() => {
// const genePropsVal = toValue(genesProps)
// refGenes.value = genePropsVal?.map(d => d)
draw()
})
const genesTextRotate = ref<Record<string, TextRotateOuput>>({})
watch(genesLabel, (newGenesLabel) => {
const genesTextRotateVal = toValue(genesTextRotate)
newGenesLabel.forEach(el => {
const textRotate = useTextRotate({ availableWidth: el.width })
genesTextRotateVal[el.id.toString()] = textRotate
})
})
watch(structureNodes, () => {
draw()
})
watch(geneNodes, () => {
watch(geneNodesWithY, () => {
draw()
})
......@@ -285,7 +321,7 @@ function drawStructure(operonGroup: d3.Selection<SVGGElement, any, SVGElement |
exit => exit.remove()
)
const imageSelection = structureSelection
.attr("transform", `translate(0,${toValue(yScale)("img")})`)
.attr("transform", `translate(0,${toValue(yScale).img})`)
.attr("cursor", d => d.highlight ? "pointer" : null)
.select("image")
imageSelection
......@@ -302,7 +338,7 @@ function drawStructure(operonGroup: d3.Selection<SVGGElement, any, SVGElement |
}
function drawGenes(operonGroup: d3.Selection<SVGGElement, any, SVGElement | null, any>) {
const genesWithCoordVal = toValue(geneNodes)
const genesWithCoordVal = toValue(geneNodesWithY)
const genes = genesWithCoordVal
const genesSelection = operonGroup
.selectAll("g.gene-grp") // get all "existing" lines in svg
......@@ -343,13 +379,12 @@ function drawGenes(operonGroup: d3.Selection<SVGGElement, any, SVGElement | null
)
genesSelection
.attr("cursor", d => d.highlight ? "pointer" : null)
.attr("transform", d => `translate(${d.x}, 0)`)
.attr("transform", d => `translate(${d.x}, ${d.y})`)
const genePathSelection = genesSelection
.select("path.gene")
// .attr("stroke", d => d?.highlight ? d3.color(color(d.system))?.darker() : null)
.attr("stroke-width", d => d?.highlight ? 4 : 0)
.attr("transform", d => `translate(0, ${d.y})`)
genePathSelection
.transition()
.attr("fill", d => d?.highlight ? d3.color(color(d.system))?.brighter() : color(d.system))
......@@ -412,15 +447,30 @@ function drawGenesLabel(operonGroup: d3.Selection<SVGGElement, any, SVGElement |
)
updateSelection
.attr("cursor", d => d.highlight ? "pointer" : null)
.select("text")
.attr("transform", d => `translate(${d.x + d.width / 2},${d.y}) rotate(40) `)
.select<SVGTextElement>("text")
.attr("transform", function (d) {
const genesTextRotateVal = toValue(genesTextRotate)
const geneId = d.id.toString()
const textRotate = genesTextRotateVal?.[geneId]
if (textRotate !== undefined) {
const { rotate, width, setTextElem, textWidth } = textRotate
setTextElem(this)
return `translate(${d.x + d.width / 2 - toValue(width) / 2},${d.y}) rotate(${toValue(rotate)}) `
}
return null
})
.attr("style", d => d.highlight ? "font-weight: 700" : null)
.text(d => d.gene)
updateSelection.select("title").text(d => d.gene)
}
function adjacentlinks(nodes: Record<string, any>) {
const links = []
for (let i = 0; i < nodes.length; i++) {
......
import { useElementSize } from '@vueuse/core'
export interface TextRotateInput {
availableWidth: MaybeRef<number>
}
export interface TextRotateOuput {
rotate: ComputedRef<number>
width: Ref<number>
height: Ref<number>
textWidth: ComputedRef<number>,
setTextElem: (newTextElement: MaybeRef<SVGTextElement>) => void
}
export function useTextRotate({ availableWidth }: TextRotateInput): TextRotateOuput {
const width = ref<number>(0)
const height = ref<number>(20)
const textElement = ref<SVGTextElement | undefined>(undefined)
const availableWidthVal = toValue(availableWidth)
const textWidth = computed(() => {
const textElementVal = toValue(textElement)
if (textElementVal !== undefined) {
const { width } = textElementVal.getBBox()
return width
}
return 0
})
function setTextElem(newTextElement: MaybeRef<SVGTextElement>) {
textElement.value = toValue(newTextElement)
}
const rotateRad = computed(() => {
const textWidthSide = textWidth.value / Math.sqrt(2)
const textWidthVal = toValue(textWidth)
if (textWidthVal === 0) return 0
if (availableWidthVal > textWidthVal) {
width.value = textWidth.value
height.value = 20
return 0
}
if (textWidthSide < availableWidthVal) {
width.value = textWidthSide
height.value = textWidthSide
return Math.PI / 4
}
else {
width.value = 0
height.value = textWidth.value
return Math.PI / 2
}
})
const rotate = computed(() => {
return rotateRad.value * 180 / Math.PI
})
watch(rotateRad, (newVal) => {
const textWidthVal = toValue(textWidth)
if (newVal === 0) {
width.value = textWidthVal
height.value = 20
}
else if (newVal > Math.PI / 4) {
width.value = 15
height.value = textWidthVal + 100
}
else {
const w = Math.sin(newVal) * textWidthVal
const h = Math.cos(newVal) * textWidthVal
height.value = h + 100
width.value = w
}
})
return { rotate, width, height, textWidth, setTextElem }
}
\ No newline at end of file
......@@ -21,6 +21,9 @@ export interface StructureOperonGeneWithCoordinate extends StructureOperonGeneWi
height: number
x: number
y: number
labelHeight: number,
// labelWidth: number,
// rotate: number
}
export interface StructureGeneLinks {
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment