kieler / klighd Goto Github PK
View Code? Open in Web Editor NEWKIELER Lightweight Diagams
License: Eclipse Public License 2.0
KIELER Lightweight Diagams
License: Eclipse Public License 2.0
Both the Eclipse-based implementation and the langauge server variant only fully support the layout option types INT, DOUBLE, BOOLEAN, and ENUM. UNDEFINED, STRING, ENUMSET and OBJECT are currently not supported or currently broken (in the case of ENUMSET). For completion, both implementations should support these remaining types, if applicable.
See klighd.ui.internal.options:LayoutOptionControlFactory and klighd.lsp:KGraphLanguageServerExtension#setLayoutOptions.
When renaming a file that is opened within a DiagramEditorPart, the editor is closed.
Expected behavior would be that the editor stays open and the internal relation of the resource is updated to the new file. Hence, also the title of the editor tab should conform to the new name.
The KGraphExporter is able to remove all semantical information from the graph during export, which is great.
However, it just replaces the string with a string of equal length (number of chars, not rendering). If the string is something like an SCChart transition label it just makes it one long word and not separate words. If I would like to share a graph, generated from an SCChart containing senstive information, that has some issue with label management I am unable to do so.
Maybe there could be several levels of "information purging", maybe with a slider in the interface, rageing from "replace all strings with complete gibberish, even replacing whitespace" to "replace all strings but keep multiple occurences of the same string consistent".
If you could go ahead and do that it would be great!
It would be a huge improvement if klighd would support some sort of styling of labels/texts.
In the attached SCChart, it would be nice to colorize the annotated parts in the labels in different colors. However, regarding SCCharts, different styles for declaration and action texts would also be beneficial.
This is related to KIPRA-1895.
The current zoom to fit behavior aligns the viewport with the size of the current diagram clip node, which might be much larger (or even smaller) than the bounding box of the actual (nested) diagram content.
This may be given in particular if the diagram clip node's (potentially large amount of) ports is turned invisible.
A dedicated zooming strategy ZOOM_TO_FIT_CONTENT would be beneficial than.
When updating a diagram with the incremental update, the SourceModelTrackingAdapter of the ViewContext breaks, the map used in the ViewContext:getSourceElement method only contains a single node. Somewhere in the last updates to the incremental update this must have confused the automatic adapter propagation that the mapping is not restored correctly anymore.
The current approach of offering a button that opens the fully fledged layout algorithm selection dialog does not conform with our intentions of providing pre-filtered reasonable layout options. Establishing an appropriate means for configuring the ALGORITHM layout option is the task denoted by this ticket.
The offscreen rendering of lots of graphs takes a considerable amount of time. It would be great to have the option to do a true parallel generation of SVG images.
This requires changes to KlighD, especially in the area of size estimation, as well as ELK, at least at the progress monitor handling.
When using the addTailArrowDecorator(KPolyline pl)
from KPolylineExtensions
to add an arrow head at the tail of an edge, the positioning is wrong.
The head arrow decorator works fine but since both seem to use the same code base, either the rotation reference point has changed at some point in the past or the position adjustments in the extension were wrong from the beginning (but I doubt that).
For the ability to find a rendering in the hierarchy of a node just by an ID, the KRenderingIdGenerator currently overwrites the ID of any non-parent rendering, which may cause problems for syntheses using that ID for something else. Also, having a parent rendering with an ID containing the separator character $ breaks interaction with that rendering on the client.
I propose to rework the code to not use the id field of the rendering, but rather a property on that element called something such as 'renderingId'
In the current master, the following .kgt file causes a weird Layout:
kgraph hierarchicEdge
properties:
de.cau.cs.kieler.kgraphsynthesis.defaults = true
org.eclipse.elk.hierarchyHandling = INCLUDE_CHILDREN
org.eclipse.elk.direction = RIGHT
knode {
krectangle {
grid: columns = 1
ktext("foo")
krectangle {
kchildArea
}
}
knode src {
kedge (-> dest:p_dest)
}
kport
}
knode dest {
kport p_dest
}
KLighD will show the graph like this in a diagram:
childInContainer.png
The edge should be a straight line leading into the destination port, not somewhere way above that. Also, the additional single port added to the left of the hierarchic node should be centered and not somewhere above the center.
Both these effects are affected by the offset of the child rendering within its node. Adding some linebreaks "\n" to the text above the renderings makes both these issues even worse.
The export to image function in KIELER creates black image, if the scaling factor is too high. If the scaling factor is lowered again the new generated image stay black until Kieler is restarted.
Recreate: open SpeedSinalDivider.sctx. Right click. save as image... . set Scale factor to 16. ok.
the exported image should now be black.
Allow the user to change the zoom- and pan behavior between the current behavior (like Google Maps) and a typical browser/document/Inkscape behavior.
Currently zooming is mapped to the mouse wheel and panning to clicking and dragging.
The other configuration lets the user scroll vertically with the mouse wheel, horizontally with shift+mouse wheel and zoom with ctrl+mouse wheel. This is what some users might prefer and mainly reduces the work one would have to do for scrolling over a bigger distance in the diagram.
de.cau.cs.kieler.klighd.piccolo.internal.controller.AbstractKGERenderingController::updateStyles()
It should be invoked after layout but before update to prevent animations from altering style adaptions (here: position changes)
When using a layout algorithm such as rectPacking that uses the node order in the file as order in which the nodes should be displayed the following may happen:
One has a graph such as this one:
algorithm: rectPacking
aspectRatio: 3
node n1
// node n4
node n2
node n3
This produces the following drawing:
Uncommenting the line produces the following drawing:
After reopening the file the following drawing is created:
This might be a problem in the incremental update.
I propose that incremental update should honor the order that is defined in the file since the current solution produces different drawings without changing the model.
If evaluateGridPlacement will be called the first time, it sets a property on the container saving arrays for the calculated row heights and column widths. If now the count of rows or columns is increased and evaluateGridPlacement is called again, it will try to still use the old arrays which leads to a ArrayIndexOutOfBounds Exception.
A possible solution: set gridSizingAvailable to false (line 183) if the size of the old arrays (spacingProperty.calculatedColumnWidths / spacingProperty.calculatedRowHeights) does not match the current size of rows (this.numRows) or columns (this.numColumns) anymore.
Previously Tooltips were not shown when the selectability was suppressed, which I 'fixed' with this commit. However, now tooltips and selection works, but any action that is on the parent rendering of the text will not be executed, because the text node is checked for actions instead of the parent rendering because it is still picked first and event handling is based on the picked node.
Action execution (and selection) should still work as told by the 'suppressSelectability' command.
When changing view related properties (such as KlighdProperties.SHOW or KlighdProperties.EXPAND) in the synthesis, the incremental update does not apply these changes.
It seems the property is either not merged into the view model or the viewer is not informed that this property changed and it should show a different rendering.
This behavior might be intended such that the incremental update keeps expansion states and hidden elements across updates.
However, it seems to be problematic, as in my case where I have a synthesis that invokes DiagramSyntheses.initiallyHide on edges depending on a synthesis option (either show all or show them interactively). However, toggling this option has no effect with incremental update, since edges keep their initial state.
I built a workaround using invisibility but is this intended?
When pasting the following code in a .kgt file in KIELER, the diagram view will show the rectangle 'outer' around a wrong point:
properties: de.cau.cs.kieler.kgraphsynthesis.defaults = true
--
knode n1 {
kedge ( -> outer)
}
knode outer {
krectangle {
styles: rotation=33
kchildArea {
styles: rotation=90
}
}
knode inner
}
The resulting diagram looks like this:
The 'outer' rectangle is rotated, but not around its center point. The inner rectangle that is rotated via the child area of the outer node neither. Now when updating the diagram (such as adding a whitespace at the end of the file) the diagram looks as follows:
Slightly better, as the outer rectangle is now rotated around the correct anchor. But the rotated child area just reapplied the rotation and is now rotated by 2*90 degrees around some other anchor point again (not the center). Another update (such as adding another whitespace at the end) causes the child area to rotate further:
So this really contains two issues:
1: The initial wrong rotation axis
2: the wrong handling of the rotation style within child area renderings
If the user zooms in the diagram view during an animation, the view immediately returns to the old animation after the user zoom, effectively undoing the user input again. When the user wants to interact with the view, it should allow that and cancel the current animation in favor of the user's command.
When calling the public getDelegate() funciton of the ReinitializingDiagramSynthesisProxy, this returns null if the delegate has never been initialized before by calling some other function of that class first. I suggest this method also gets this existence check when getting the delegate as most other functions have as well:
if (this.transformationDelegate == null) {
this.transformationDelegate = getNewDelegateInstance();
}
@sailingKieler Was there any reason why this is not checked when getting the delegate directly? I currently have a use case here where I sometimes get a NPE because of this, which could be fixed there by just putting a toString() and not using it, just so that the delegate is initialized. That solution however is a quite hacky way that does not seem intended. I'll create a PR to solve this if you think this does no other harm.
It would be nice if we could specify tooltips for synthesis options. Usually the name is not sufficient for documenting the effect of the option.
The sidebar already displays tooltips for each option but it displays only the label itself.
When a (long running) layout is triggered and the diagram view is closed before the layout finishes, an "Widgets has been disposed" exception is thrown.
Stacktrace:
org.eclipse.swt.SWTException: Widget is disposed
--
at org.eclipse.swt.SWT.error(SWT.java:4533)
at org.eclipse.swt.SWT.error(SWT.java:4448)
at org.eclipse.swt.SWT.error(SWT.java:4419)
at org.eclipse.swt.widgets.Widget.error(Widget.java:482)
at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:354)
at org.eclipse.swt.widgets.Widget.addListener(Widget.java:199)
at de.cau.cs.kieler.klighd.piccolo.internal.KlighdCanvas$KlighdRoot.getDefaultInputManager(KlighdCanvas.java:142)
at de.cau.cs.kieler.klighd.piccolo.internal.KlighdCanvas.sendInputEventToInputManager(KlighdCanvas.java:223)
at de.cau.cs.kieler.klighd.piccolo.internal.events.KlighdMouseEventListener$1.run(KlighdMouseEventListener.java:169)
When KLighD animates the new layout, not only the set animation time of 0.5 seconds, but also the model size seems to influence the animation time. Some large models that take longer to draw don't finish their animation in 0.5 seconds but sometimes in 2 or more seconds. This should be consistent and follow the 0.5 seconds, no matter how big the model is.
When creating a new synthesis option of type range option with only int values as their lower and upper bound, step size and initial value the slider and current option initially works as intended.
public static final SynthesisOption DESCRIPTION_LENGTH = SynthesisOption.createRangeOption("Description text length", 0, 500, 1, 20).setCategory(BUNDLE)
And only shows int values and also returns an int when the option value is read a synthesis as in
usedContext.getOptionValue(DESCRIPTION_LENGTH) as Integer
When Eclipse is closed with an option value such as '42' and re-opened the slider shows the current value as a float '42.0' and also returns that float when read from the context which may cause the synthesis to have unexpected behavior.
Solution: The int-only slider should persist its value as an int if it was an int and not restore it as a float.
If a diagram is modified on-the-fly in the view (e.g. nodes are removed by an action) the incremental update requires two runs until changes in the model (new synthesis run) are correctly applied to the diagram. The first result is always broken.
Tracing of elements was previously implemented by adding a trace URI with the help of Sprotty's API during the generation of the diagram. As that increased the diagram generation time by almost 50%, this solution is broken and should be reimplemented in an ad-hoc style, so the tracing data of elements is only acquired whenever it is needed by a user request. See the current code in KGraphDiagramGenerator
Exporting SVGs with the extended freehep exporter yields in wrong font sizes. This is especially bad for multiline text.
In case an Eclipse-based diagram part has been closed while executing a long-running (i)action, the subsequent asynchronously executed layout computation fails in LightDiagramServices at
if (thePart instanceof ILayoutConfigProvider) {
extendedConfigurator
.overrideWith(((ILayoutConfigProvider) thePart).getLayoutConfig());
}
since extendedConfigurator.overrideWith() expects something none-null.
In the interactive layered approach nodes are always shifted.
However want could think of a user that wants to have connectd nodes n the same layer. I suggest to allow a user to do exactly that and handle it without shifting (as an option).
@a-sr @NiklasRentzCAU I would like to remove this code, as getGraphElement().getProperties().contains(KlighdProperties.NOT_SELECTABLE)
will never be satisfied. getProperties()
returns an EMap<IProperty<?>, Object>
containing IPropertyToObjectMapImpl
s. Hence, KlighdProperties.NOT_SELECTABLE
will never be "contained".
Also from a conceptual point of view I don't like these kind of built-in magics.
If I remember right, back then I decided leave the freedom of fine-grained control on the selectability of labels and text fields to the diagram synthesis developers on purpose.
Any objections?
For the implementation of the KLighD language server extension and diagram server, some implementations used in Piccolo and the UI plugins using Eclipse UI have been duplicated to avoid unwanted dependencies from the lsp bundle to Piccolo or Eclipse UI, and because the implementations slightly differ in how information is stored. For consistency and to avoid code duplications, these should be moved to a more central bundle and reused in both cases.
This includes the following implementations:
In several classes the uri/clientId is only called key. To clarify what is needed I suggest to rename them to uri or clientId.
The composite update site https://kieler.github.io/KLighD/ currently has no index.html page (as the other update sites) and github.io reports a 404.
Could you please add a page for that update site such that users get feedback whether this page is available and working.
In some diagrams or sometimes in different layout runs, regions (renderings with a child area) disappear because the get a height of zero.
The screenshot shows the motor example, with two region renderings missing. The layout view shows that they exists but have no height.
To work with the data of the diagram outside of the eclipse editor, it would be nice to be able to copy the text of single elements of the graph. Maybe the copied text could be formatted, too:
|Element|
|Description|
copy -->
Element (Description)
The ECore model of KRendering defines that every element in the dashPattern List of KLineStyle needs to be unique. That causes custom patterns like '3 1 1 1' to not work as expected (it should create a dash-dot pattern with small spaces inbetween), and just show a '3 1' pattern in every resulting KGraph (it creates just a dash pattern with small spaces inbetween).
The attached image shows the bug: the left is how it is supposed to look (created here with the predefined dash-dot) and the right is how it is rendered currently.
For the klighd diagram server the sprotty actions are manually registered.
I suggest to use services instead.
With #24 KLighD offers zoom to fit strategy that doesn't fit the current diagram clip node's bounds into the available canvas area, but the bounding box of its displayed contents.
This make a difference for hand-drawn layouts with large container nodes and little content.
Such a strategy variant would be beneficial for the zoom to focus strategy, too.
Thus, this issue demands for a ZOOM_TO_FOCUS_[ON_]CONTENT
entry in ZoomStyle.java and the required implementations.
The resource edits in interactive mode are just overriding the text file. Such an update cannot be reverted via ctrl+z.
SVG exports of diagrams suffer (and will always suffer) from the different text renderings on different machines, and worse, even in different browsers on the same machine.
I just figured out that the SVG elements <text>
and <tspan>
offer the attributes textLength
and lengthAdjust
, which I wasn't aware of. See also https://codepen.io/tigt/post/more-robust-svg-text-with-lengthadjust-and-font-size-adjust
Hence, it would be valuable to add the designated textLength
value to the generated SVG output and check whether that improves the portability of SVG exports.
The following behaviour was observed when clipping a diagram with clip node's ports hidden:
image-2019-09-26-13-30-55-067.png
Constraint: A dedicated KChildArea is contained in the figure description of the clipped node.
Expected:
The Klighd View should not always pop-up to the front when the editor is switched. Maybe another toggle would do the trick.
Using the Tilling Options dialogue leads to a too small window where you can't read the values of rows and columns.
In addition the following message is written on the console:
*** BUG ***
In pixman_region32_init_rect: Invalid rectangle passed
Set a breakpoint on '_pixman_log_error' to debug
In some client project the ability to compose KLabel
s of several KText
element would be extremely valuable. On the one hand those KText
s shall be stylable differently as well as independently selectable or react on clicks with different actions.
Caveat: Labels size computations by layout algorithms (like GraphViz dot) will be impaired for such view models. Consequently, the diagram synthesis developer is in charge of configuring things right.
Label management issues lots of calls to estimateTextSize(...), which makes it rather slow. We see two possibilities to solve it:
Currently only syntheses are registered via service loader and in a plugin.xml
.
To successfully use KLighD in a non Eclipse context one should also register the actions and not only the syntheses via a service loader.
If the diagram drawing is clipped to the child area of a (nested) compound diagram node, and a subsequent layout run determines new coordinates for current clip node (or for some of its transitive parents) the viewport of the main diagram window appears to jump away, see
first layout (main diagram on the right):
The truth is, that the viewport stays in place as revealed by the outline (on the left), whereas position vector of the clip node changed, and with that the accumulated position vectors of the child nodes shown in the main diagram.
This issue demands for a smart update of the viewports position vector yielding a stable position of the viewport in relation to the current clip node during the application of diagram layout data.
Sometimes after printing, the print dialog stops working. I am not sure, why this is happening, but you are at least able to print once after program start. Here is the exception:
!ENTRY org.eclipse.ui 4 0 2018-08-28 13:08:06.659
--
!ENTRY org.eclipse.ui 4 0 2018-08-28 13:08:06.659
!MESSAGE Unhandled event loop exception
!STACK 0 org.eclipse.e4.core.di.InjectionException: org.eclipse.core.commands.ExecutionException: While executing the action, an exception occurred at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:65) at org.eclipse.e4.core.internal.di.InjectorImpl.invokeUsingClass(InjectorImpl.java:305) at org.eclipse.e4.core.internal.di.InjectorImpl.invoke(InjectorImpl.java:239) at org.eclipse.e4.core.contexts.ContextInjectionFactory.invoke(ContextInjectionFactory.java:132) at org.eclipse.e4.core.commands.internal.HandlerServiceHandler.execute(HandlerServiceHandler.java:152) at org.eclipse.core.commands.Command.executeWithChecks(Command.java:494) at org.eclipse.core.commands.ParameterizedCommand.executeWithChecks(ParameterizedCommand.java:487) at org.eclipse.e4.core.commands.internal.HandlerServiceImpl.executeHandler(HandlerServiceImpl.java:210) at org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher.executeCommand(KeyBindingDispatcher.java:287) at org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher.press(KeyBindingDispatcher.java:527) at org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher.processKeyEvent(KeyBindingDispatcher.java:577) at org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher.filterKeySequenceBindings(KeyBindingDispatcher.java:385) at org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher.access$0(KeyBindingDispatcher.java:331) at org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher$KeyDownFilter.handleEvent(KeyBindingDispatcher.java:88) at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:86) at org.eclipse.swt.widgets.Display.filterEvent(Display.java:1271) at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1078) at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1103) at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1088) at org.eclipse.swt.widgets.Widget.sendKeyEvent(Widget.java:1130) at org.eclipse.swt.widgets.Widget.sendKeyEvent(Widget.java:1126) at org.eclipse.swt.widgets.Widget.wmChar(Widget.java:1547) at org.eclipse.swt.widgets.Control.WM_CHAR(Control.java:4962) at org.eclipse.swt.widgets.Control.windowProc(Control.java:4843) at org.eclipse.swt.widgets.Display.windowProc(Display.java:5178) at org.eclipse.swt.internal.win32.OS.DispatchMessageW(Native Method) at org.eclipse.swt.internal.win32.OS.DispatchMessage(OS.java:2560) at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3815) at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine$5.run(PartRenderingEngine.java:1150) at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:336) at org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine.run(PartRenderingEngine.java:1039) at org.eclipse.e4.ui.internal.workbench.E4Workbench.createAndRunUI(E4Workbench.java:153) at org.eclipse.ui.internal.Workbench.lambda$3(Workbench.java:680) at org.eclipse.ui.internal.Workbench$$Lambda$14/977195776.run(Unknown Source) at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:336) at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:594) at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:148) at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:151) at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196) at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:134) at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:104) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:388) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:243) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:653) at org.eclipse.equinox.launcher.Main.basicRun(Main.java:590) at org.eclipse.equinox.launcher.Main.run(Main.java:1499) at org.eclipse.equinox.launcher.Main.main(Main.java:1472)
Caused by: org.eclipse.core.commands.ExecutionException: While executing the action, an exception occurred at org.eclipse.jface.commands.ActionHandler.execute(ActionHandler.java:123) at org.eclipse.ui.internal.handlers.E4HandlerProxy.execute(E4HandlerProxy.java:92) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.eclipse.e4.core.internal.di.MethodRequestor.execute(MethodRequestor.java:55) ... 50 more
Caused by: org.eclipse.swt.SWTException: Widget is disposed at org.eclipse.swt.SWT.error(SWT.java:4533) at org.eclipse.swt.SWT.error(SWT.java:4448) at org.eclipse.swt.SWT.error(SWT.java:4419) at org.eclipse.swt.widgets.Widget.error(Widget.java:482) at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:354) at org.eclipse.swt.widgets.Control.getShell(Control.java:1589) at de.cau.cs.kieler.klighd.ui.printing.PrintAction.run(PrintAction.java:150) at org.eclipse.jface.action.Action.runWithEvent(Action.java:473) at org.eclipse.jface.commands.ActionHandler.execute(ActionHandler.java:118) ... 56 more
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.